Home | History | Annotate | Download | only in internal
      1 package com.google.inject.internal;
      2 
      3 import com.google.common.base.Preconditions;
      4 import com.google.common.collect.ImmutableList;
      5 import com.google.common.collect.ImmutableMap;
      6 import com.google.common.collect.ListMultimap;
      7 import com.google.common.collect.Lists;
      8 import com.google.common.collect.Maps;
      9 import com.google.inject.Injector;
     10 import com.google.inject.Key;
     11 import com.google.inject.Provider;
     12 import com.google.inject.ProvisionException;
     13 import com.google.inject.Scope;
     14 import com.google.inject.Scopes;
     15 import com.google.inject.Singleton;
     16 import com.google.inject.internal.CycleDetectingLock.CycleDetectingLockFactory;
     17 import com.google.inject.spi.Dependency;
     18 import com.google.inject.spi.DependencyAndSource;
     19 import com.google.inject.spi.Message;
     20 
     21 import java.util.Collections;
     22 import java.util.List;
     23 import java.util.Map;
     24 
     25 /**
     26  * One instance per {@link Injector}. Also see {@code @}{@link Singleton}.
     27  *
     28  * Introduction from the author:
     29  * Implementation of this class seems unreasonably complicated at the first sight.
     30  * I fully agree with you, that the beast below is very complex
     31  * and it's hard to reason on how does it work or not.
     32  * Still I want to assure you that hundreds(?) of hours were thrown
     33  * into making this code simple, while still maintaining Singleton contract.
     34  *
     35  * Anyway, why is it so complex? Singleton scope does not seem to be that unique.
     36  * 1) Guice has never truly expected to be used in multi threading environment
     37  *    with many Injectors working alongside each other. There is almost no
     38  *    code with Guice that propagates state between threads. And Singleton
     39  *    scope is The exception.
     40  * 2) Guice supports circular dependencies and thus manages proxy objects.
     41  *    There is no interface that allows user defined Scopes to create proxies,
     42  *    it is expected to be done by Guice. Singleton scope needs to be
     43  *    able to detect circular dependencies spanning several threads,
     44  *    therefore Singleton scope needs to be able to create these proxies.
     45  * 3) To make things worse, Guice has a very tricky definition for a binding
     46  *    resolution when Injectors are in in a parent/child relationship.
     47  *    And Scope does not have access to this information by design,
     48  *    the only real action that Scope can do is to call or not to call a creator.
     49  * 4) There is no readily available code in Guice that can detect a potential
     50  *    deadlock, and no code for handling dependency cycles spanning several threads.
     51  *    This is significantly harder as all the dependencies in a thread at runtime
     52  *    can be represented with a list, where in a multi threaded environment
     53  *    we have more complex dependency trees.
     54  * 5) Guice has a pretty strong contract regarding Garbage Collection,
     55  *    which often prevents us from linking objects directly.
     56  *    So simple domain specific code can not be written and intermediary
     57  *    id objects need to be managed.
     58  * 6) Guice is relatively fast and we should not make things worse.
     59  *    We're trying our best to optimize synchronization for speed and memory.
     60  *    Happy path should be almost as fast as in a single threaded solution
     61  *    and should not take much more memory.
     62  * 7) Error message generation in Guice was not meant to be used like this and to work around
     63  *    its APIs we need a lot of code. Additional complexity comes from inherent data races
     64  *    as message is only generated when failure occurs on proxy object generation.
     65  * Things get ugly pretty fast.
     66  *
     67  * @see #scope(Key, Provider)
     68  * @see CycleDetectingLock
     69  *
     70  * @author timofeyb (Timothy Basanov)
     71  */
     72 public class SingletonScope implements Scope {
     73 
     74   /** A sentinel value representing null. */
     75   private static final Object NULL = new Object();
     76 
     77   /**
     78    * Allows us to detect when circular proxies are necessary. It's only used during singleton
     79    * instance initialization, after initialization direct access through volatile field is used.
     80    *
     81    * NB: Factory uses {@link Key}s as a user locks ids, different injectors can
     82    * share them. Cycles are detected properly as cycle detection does not rely on user locks ids,
     83    * but error message generated could be less than ideal.
     84    *
     85    * TODO(user): we may use one factory per injector tree for optimization reasons
     86    */
     87   private static final CycleDetectingLockFactory<Key<?>> cycleDetectingLockFactory =
     88       new CycleDetectingLockFactory<Key<?>>();
     89 
     90   /**
     91    * Provides singleton scope with the following properties:
     92    * - creates no more than one instance per Key as a creator is used no more than once,
     93    * - result is cached and returned quickly on subsequent calls,
     94    * - exception in a creator is not treated as instance creation and is not cached,
     95    * - creates singletons in parallel whenever possible,
     96    * - waits for dependent singletons to be created even across threads and when dependencies
     97    *   are shared as long as no circular dependencies are detected,
     98    * - returns circular proxy only when circular dependencies are detected,
     99    * - aside from that, blocking synchronization is only used for proxy creation and initialization,
    100    * @see CycleDetectingLockFactory
    101    */
    102   public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
    103     /**
    104      * Locking strategy:
    105      * - volatile instance: double-checked locking for quick exit when scope is initialized,
    106      * - constructionContext: manipulations with proxies list or instance initialization
    107      * - creationLock: singleton instance creation,
    108      *   -- allows to guarantee only one instance per singleton,
    109      *   -- special type of a lock, that prevents potential deadlocks,
    110      *   -- guards constructionContext for all operations except proxy creation
    111      */
    112     return new Provider<T>() {
    113       /**
    114        * The lazily initialized singleton instance. Once set, this will either have type T or will
    115        * be equal to NULL. Would never be reset to null.
    116        */
    117       volatile Object instance;
    118 
    119       /**
    120        * Circular proxies are used when potential deadlocks are detected. Guarded by itself.
    121        * ConstructionContext is not thread-safe, so each call should be synchronized.
    122        */
    123       final ConstructionContext<T> constructionContext = new ConstructionContext<T>();
    124 
    125       /** For each binding there is a separate lock that we hold during object creation. */
    126       final CycleDetectingLock<Key<?>> creationLock = cycleDetectingLockFactory.create(key);
    127 
    128       @SuppressWarnings("DoubleCheckedLocking")
    129       public T get() {
    130         // cache volatile variable for the usual case of already initialized object
    131         final Object initialInstance = instance;
    132         if (initialInstance == null) {
    133           // instance is not initialized yet
    134 
    135           // acquire lock for current binding to initialize an instance
    136           final ListMultimap<Long, Key<?>> locksCycle =
    137               creationLock.lockOrDetectPotentialLocksCycle();
    138           if (locksCycle.isEmpty()) {
    139             // this thread now owns creation of an instance
    140             try {
    141               // intentionally reread volatile variable to prevent double initialization
    142               if (instance == null) {
    143                 // creator throwing an exception can cause circular proxies created in
    144                 // different thread to never be resolved, just a warning
    145                 T provided = creator.get();
    146                 Object providedNotNull = provided == null ? NULL : provided;
    147 
    148                 // scope called recursively can initialize instance as a side effect
    149                 if (instance == null) {
    150                   // instance is still not initialized, se we can proceed
    151 
    152                   // don't remember proxies created by Guice on circular dependency
    153                   // detection within the same thread; they are not real instances to cache
    154                   if (Scopes.isCircularProxy(provided)) {
    155                     return provided;
    156                   }
    157 
    158                   synchronized (constructionContext) {
    159                     // guarantee thread-safety for instance and proxies initialization
    160                     instance = providedNotNull;
    161                     constructionContext.setProxyDelegates(provided);
    162                   }
    163                 } else {
    164                   // safety assert in case instance was initialized
    165                   Preconditions.checkState(instance == providedNotNull,
    166                       "Singleton is called recursively returning different results");
    167                 }
    168               }
    169             } catch (RuntimeException e) {
    170               // something went wrong, be sure to clean a construction context
    171               // this helps to prevent potential memory leaks in circular proxies list
    172               synchronized (constructionContext) {
    173                 constructionContext.finishConstruction();
    174               }
    175               throw e;
    176             } finally {
    177               // always release our creation lock, even on failures
    178               creationLock.unlock();
    179             }
    180           } else {
    181             // potential deadlock detected, creation lock is not taken by this thread
    182             synchronized (constructionContext) {
    183               // guarantee thread-safety for instance and proxies initialization
    184               if (instance == null) {
    185                 // InjectorImpl.callInContext() sets this context when scope is called from Guice
    186                 Map<Thread, InternalContext> globalInternalContext =
    187                     InjectorImpl.getGlobalInternalContext();
    188                 InternalContext internalContext = globalInternalContext.get(Thread.currentThread());
    189 
    190                 // creating a proxy to satisfy circular dependency across several threads
    191                 Dependency<?> dependency = Preconditions.checkNotNull(
    192                     internalContext.getDependency(),
    193                     "globalInternalContext.get(currentThread()).getDependency()");
    194                 Class<?> rawType = dependency.getKey().getTypeLiteral().getRawType();
    195 
    196                 try {
    197                   @SuppressWarnings("unchecked")
    198                   T proxy = (T) constructionContext.createProxy(
    199                       new Errors(), internalContext.getInjectorOptions(), rawType);
    200                   return proxy;
    201                 } catch (ErrorsException e) {
    202                   // best effort to create a rich error message
    203                   List<Message> exceptionErrorMessages = e.getErrors().getMessages();
    204                   // we expect an error thrown
    205                   Preconditions.checkState(exceptionErrorMessages.size() == 1);
    206                   // explicitly copy the map to guarantee iteration correctness
    207                   // it's ok to have a data race with other threads that are locked
    208                   Message cycleDependenciesMessage = createCycleDependenciesMessage(
    209                       ImmutableMap.copyOf(globalInternalContext),
    210                       locksCycle,
    211                       exceptionErrorMessages.get(0));
    212                   // adding stack trace generated by us in addition to a standard one
    213                   throw new ProvisionException(ImmutableList.of(
    214                       cycleDependenciesMessage, exceptionErrorMessages.get(0)));
    215                 }
    216               }
    217             }
    218           }
    219           // at this point we're sure that singleton was initialized,
    220           // reread volatile variable to catch all corner cases
    221 
    222           // caching volatile variable to minimize number of reads performed
    223           final Object initializedInstance = instance;
    224           Preconditions.checkState(initializedInstance != null,
    225               "Internal error: Singleton is not initialized contrary to our expectations");
    226           @SuppressWarnings("unchecked")
    227           T initializedTypedInstance = (T) initializedInstance;
    228           return initializedInstance == NULL ? null : initializedTypedInstance;
    229         } else {
    230           // singleton is already initialized and local cache can be used
    231           @SuppressWarnings("unchecked")
    232           T typedInitialIntance = (T) initialInstance;
    233           return initialInstance == NULL ? null : typedInitialIntance;
    234         }
    235       }
    236 
    237       /**
    238        * Helper method to create beautiful and rich error descriptions. Best effort and slow.
    239        * Tries its best to provide dependency information from injectors currently available
    240        * in a global internal context.
    241        *
    242        * <p>The main thing being done is creating a list of Dependencies involved into
    243        * lock cycle across all the threads involved. This is a structure we're creating:
    244        * <pre>
    245        * { Current Thread, C.class, B.class, Other Thread, B.class, C.class, Current Thread }
    246        * To be inserted in the beginning by Guice: { A.class, B.class, C.class }
    247        * </pre>
    248        * When we're calling Guice to create A and it fails in the deadlock while trying to
    249        * create C, which is being created by another thread, which waits for B. List would
    250        * be reversed before printing it to the end user.
    251        */
    252       private Message createCycleDependenciesMessage(
    253           Map<Thread, InternalContext> globalInternalContext,
    254           ListMultimap<Long, Key<?>> locksCycle,
    255           Message proxyCreationError) {
    256         // this is the main thing that we'll show in an error message,
    257         // current thread is populate by Guice
    258         List<Object> sourcesCycle = Lists.newArrayList();
    259         sourcesCycle.add(Thread.currentThread());
    260         // temp map to speed up look ups
    261         Map<Long, Thread> threadById = Maps.newHashMap();
    262         for (Thread thread : globalInternalContext.keySet()) {
    263           threadById.put(thread.getId(), thread);
    264         }
    265         for (long lockedThreadId : locksCycle.keySet()) {
    266           Thread lockedThread = threadById.get(lockedThreadId);
    267           List<Key<?>> lockedKeys = Collections.unmodifiableList(locksCycle.get(lockedThreadId));
    268           if (lockedThread == null) {
    269             // thread in a lock cycle is already terminated
    270             continue;
    271           }
    272           List<DependencyAndSource> dependencyChain = null;
    273           boolean allLockedKeysAreFoundInDependencies = false;
    274           // thread in a cycle is still present
    275           InternalContext lockedThreadInternalContext = globalInternalContext.get(lockedThread);
    276           if (lockedThreadInternalContext != null) {
    277             dependencyChain = lockedThreadInternalContext.getDependencyChain();
    278 
    279             // check that all of the keys are still present in dependency chain in order
    280             List<Key<?>> lockedKeysToFind = Lists.newLinkedList(lockedKeys);
    281             // check stack trace of the thread
    282             for (DependencyAndSource d : dependencyChain) {
    283               Dependency<?> dependency = d.getDependency();
    284               if (dependency == null) {
    285                 continue;
    286               }
    287               if (dependency.getKey().equals(lockedKeysToFind.get(0))) {
    288                 lockedKeysToFind.remove(0);
    289                 if (lockedKeysToFind.isEmpty()) {
    290                   // everything is found!
    291                   allLockedKeysAreFoundInDependencies = true;
    292                   break;
    293                 }
    294               }
    295             }
    296           }
    297           if (allLockedKeysAreFoundInDependencies) {
    298             // all keys are present in a dependency chain of a thread's last injector,
    299             // highly likely that we just have discovered a dependency
    300             // chain that is part of a lock cycle starting with the first lock owned
    301             Key<?> firstLockedKey = lockedKeys.get(0);
    302             boolean firstLockedKeyFound = false;
    303             for (DependencyAndSource d : dependencyChain) {
    304               Dependency<?> dependency = d.getDependency();
    305               if (dependency == null) {
    306                 continue;
    307               }
    308               if (firstLockedKeyFound) {
    309                 sourcesCycle.add(dependency);
    310                 sourcesCycle.add(d.getBindingSource());
    311               } else if (dependency.getKey().equals(firstLockedKey)) {
    312                 firstLockedKeyFound = true;
    313                 // for the very first one found we don't care why, so no dependency is added
    314                 sourcesCycle.add(d.getBindingSource());
    315               }
    316             }
    317           } else {
    318             // something went wrong and not all keys are present in a state of an injector
    319             // that was used last for a current thread.
    320             // let's add all keys we're aware of, still better than nothing
    321             sourcesCycle.addAll(lockedKeys);
    322           }
    323           // mentions that a tread is a part of a cycle
    324           sourcesCycle.add(lockedThread);
    325         }
    326         return new Message(
    327             sourcesCycle,
    328             String.format("Encountered circular dependency spanning several threads. %s",
    329                 proxyCreationError.getMessage()),
    330             null);
    331       }
    332 
    333       @Override
    334       public String toString() {
    335         return String.format("%s[%s]", creator, Scopes.SINGLETON);
    336       }
    337     };
    338   }
    339 
    340   @Override public String toString() {
    341     return "Scopes.SINGLETON";
    342   }
    343 }
    344