/mycila-guice/tags/mycila-guice-2.1.ga/src/main/java/com/mycila/inject/scope/ConcurrentSingleton.java

http://mycila.googlecode.com/ · Java · 183 lines · 134 code · 20 blank · 29 comment · 15 complexity · ae97e2810889989aa09ad1b41408ec8d MD5 · raw file

  1. /**
  2. * Copyright (C) 2010 Mycila <mathieu.carbou@gmail.com>
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.mycila.inject.scope;
  17. import com.google.inject.Binding;
  18. import com.google.inject.Inject;
  19. import com.google.inject.Injector;
  20. import com.google.inject.Key;
  21. import com.google.inject.Provider;
  22. import com.mycila.inject.MycilaGuiceException;
  23. import com.mycila.inject.annotation.Jsr250Singleton;
  24. import javax.annotation.PreDestroy;
  25. import java.lang.ref.Reference;
  26. import java.lang.ref.WeakReference;
  27. import java.util.concurrent.Callable;
  28. import java.util.concurrent.CountDownLatch;
  29. import java.util.concurrent.ExecutionException;
  30. import java.util.concurrent.ExecutorService;
  31. import java.util.concurrent.FutureTask;
  32. import java.util.concurrent.SynchronousQueue;
  33. import java.util.concurrent.ThreadFactory;
  34. import java.util.concurrent.ThreadPoolExecutor;
  35. import java.util.concurrent.TimeUnit;
  36. import java.util.concurrent.atomic.AtomicLong;
  37. @Jsr250Singleton
  38. public final class ConcurrentSingleton extends MycilaScope {
  39. final long expirationDelay;
  40. public ConcurrentSingleton(long expirationDelay, TimeUnit unit) {
  41. this.expirationDelay = unit.toMillis(expirationDelay);
  42. }
  43. private final FutureInjector futureInjector = new FutureInjector();
  44. private final ExecutorService executor = new ThreadPoolExecutor(
  45. 0, Runtime.getRuntime().availableProcessors() * 10,
  46. 5, TimeUnit.SECONDS,
  47. new SynchronousQueue<Runnable>(),
  48. new DefaultThreadFactory("@" + ConcurrentSingleton.class.getSimpleName() + "-Thread-"),
  49. new ThreadPoolExecutor.DiscardOldestPolicy());
  50. @Inject
  51. public void initFuture(Injector injector) {
  52. futureInjector.setInjector(injector);
  53. }
  54. @PreDestroy
  55. public void shutdown() {
  56. executor.shutdown();
  57. }
  58. @Override
  59. public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
  60. // Create a monitoring thread for this singleton.
  61. // This thread will wait for the key to be available and call the provider to get the singleton.
  62. // This thread must also be non bloquant so that it can be interrupted if the program finishes
  63. executor.execute(new Runnable() {
  64. @Override
  65. public void run() {
  66. try {
  67. // this thread will expire if not ended within the given timeout
  68. final long expirationTime = System.currentTimeMillis() + expirationDelay;
  69. while (!Thread.currentThread().isInterrupted() && System.currentTimeMillis() < expirationTime) {
  70. final Injector injector = futureInjector.waitAndGet(500, TimeUnit.MILLISECONDS).get();
  71. if (injector == null) {
  72. // May not be ready now. Retry later.
  73. Thread.sleep(500);
  74. } else {
  75. final Binding<?> binding = injector.getExistingBinding(key);
  76. if (binding == null) {
  77. // wait: perhaps it is not yet available to be constructed
  78. Thread.sleep(500);
  79. } else {
  80. try {
  81. // call the provider it load the singleton in this thread
  82. binding.getProvider().get();
  83. } catch (Throwable ignored) {
  84. // completely ignore the exception: since this provider calls the FutureProvider,
  85. // the FutureTask will memoize the exception and rethrow it for subsequent calls
  86. // not within this loader thread
  87. }
  88. return;
  89. }
  90. }
  91. }
  92. } catch (InterruptedException e) {
  93. Thread.currentThread().interrupt();
  94. }
  95. }
  96. });
  97. // Future task that will memoize the provided singleton.
  98. // The task will be run either in the caller thread, or by the monitoring thread
  99. return new FutureProvider<T>(key, unscoped);
  100. }
  101. private static final class FutureInjector {
  102. private volatile WeakReference<Injector> injector = new WeakReference<Injector>(null);
  103. private final CountDownLatch injectorAvailable = new CountDownLatch(1);
  104. public void setInjector(Injector injector) {
  105. if (this.injector.get() != null) return;
  106. this.injector = new WeakReference<Injector>(injector);
  107. injectorAvailable.countDown();
  108. }
  109. public Reference<Injector> waitAndGet(long timeout, TimeUnit unit) throws InterruptedException {
  110. // We need to apply a timeout in case the user forgot to request injection on the scope to not block the threads.
  111. // If the injector is not ready within this timeout, we consider it as inexisting
  112. injectorAvailable.await(timeout, unit);
  113. return injector;
  114. }
  115. }
  116. private static final class FutureProvider<T> extends FutureTask<T> implements Provider<T> {
  117. private final Key<T> key;
  118. private FutureProvider(Key<T> key, final Provider<T> unscoped) {
  119. super(new Callable<T>() {
  120. @Override
  121. public T call() throws Exception {
  122. return unscoped.get();
  123. }
  124. });
  125. this.key = key;
  126. }
  127. @Override
  128. public T get() {
  129. try {
  130. if (!isDone()) run();
  131. return super.get();
  132. } catch (ExecutionException e) {
  133. throw MycilaGuiceException.runtime(e);
  134. } catch (InterruptedException e) {
  135. Thread.currentThread().interrupt();
  136. throw MycilaGuiceException.runtime(e);
  137. }
  138. }
  139. @Override
  140. public String toString() {
  141. return "FutureProvider[" + key + "]";
  142. }
  143. }
  144. private static final class DefaultThreadFactory implements ThreadFactory {
  145. private final ThreadGroup group;
  146. private final AtomicLong threadNumber = new AtomicLong();
  147. private final String namePrefix;
  148. private DefaultThreadFactory(String namePrefix) {
  149. SecurityManager s = System.getSecurityManager();
  150. this.group = s != null ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
  151. this.namePrefix = namePrefix;
  152. }
  153. @Override
  154. public Thread newThread(Runnable r) {
  155. Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
  156. if (t.isDaemon())
  157. t.setDaemon(false);
  158. if (t.getPriority() != Thread.NORM_PRIORITY)
  159. t.setPriority(Thread.NORM_PRIORITY);
  160. return t;
  161. }
  162. }
  163. }