package com.google.inject.internal;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Scope;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.internal.CycleDetectingLock.CycleDetectingLockFactory;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Message;
import java.util.Formatter;
import java.util.List;
One instance per Injector
. Also see @
Singleton
. Introduction from the author: Implementation of this class seems unreasonably complicated at
the first sight. I fully agree with you, that the beast below is very complex and it's hard to
reason on how does it work or not. Still I want to assure you that hundreds(?) of hours were
thrown into making this code simple, while still maintaining Singleton contract.
Anyway, why is it so complex? Singleton scope does not seem to be that unique.
- Guice has never truly expected to be used in multi threading environment with many
Injectors working alongside each other. There is almost no code with Guice that propagates
state between threads. And Singleton scope is The exception.
- Guice supports circular dependencies and thus manages proxy objects. There is no interface
that allows user defined Scopes to create proxies, it is expected to be done by Guice.
Singleton scope needs to be able to detect circular dependencies spanning several threads,
therefore Singleton scope needs to be able to create these proxies.
- To make things worse, Guice has a very tricky definition for a binding resolution when
Injectors are in in a parent/child relationship. And Scope does not have access to this
information by design, the only real action that Scope can do is to call or not to call a
creator.
- There is no readily available code in Guice that can detect a potential deadlock, and no
code for handling dependency cycles spanning several threads. This is significantly harder
as all the dependencies in a thread at runtime can be represented with a list, where in a
multi threaded environment we have more complex dependency trees.
- Guice has a pretty strong contract regarding Garbage Collection, which often prevents us
from linking objects directly. So simple domain specific code can not be written and
intermediary id objects need to be managed.
- Guice is relatively fast and we should not make things worse. We're trying our best to
optimize synchronization for speed and memory. Happy path should be almost as fast as in a
single threaded solution and should not take much more memory.
- Error message generation in Guice was not meant to be used like this and to work around its
APIs we need a lot of code. Additional complexity comes from inherent data races as message
is only generated when failure occurs on proxy object generation. Things get ugly pretty
fast.
Author: timofeyb (Timothy Basanov) See Also:
/**
* One instance per {@link Injector}. Also see {@code @}{@link Singleton}.
*
* <p>Introduction from the author: Implementation of this class seems unreasonably complicated at
* the first sight. I fully agree with you, that the beast below is very complex and it's hard to
* reason on how does it work or not. Still I want to assure you that hundreds(?) of hours were
* thrown into making this code simple, while still maintaining Singleton contract.
*
* <p>Anyway, why is it so complex? Singleton scope does not seem to be that unique.
*
* <ol>
* <li>Guice has never truly expected to be used in multi threading environment with many
* Injectors working alongside each other. There is almost no code with Guice that propagates
* state between threads. And Singleton scope is The exception.
* <li>Guice supports circular dependencies and thus manages proxy objects. There is no interface
* that allows user defined Scopes to create proxies, it is expected to be done by Guice.
* Singleton scope needs to be able to detect circular dependencies spanning several threads,
* therefore Singleton scope needs to be able to create these proxies.
* <li>To make things worse, Guice has a very tricky definition for a binding resolution when
* Injectors are in in a parent/child relationship. And Scope does not have access to this
* information by design, the only real action that Scope can do is to call or not to call a
* creator.
* <li>There is no readily available code in Guice that can detect a potential deadlock, and no
* code for handling dependency cycles spanning several threads. This is significantly harder
* as all the dependencies in a thread at runtime can be represented with a list, where in a
* multi threaded environment we have more complex dependency trees.
* <li>Guice has a pretty strong contract regarding Garbage Collection, which often prevents us
* from linking objects directly. So simple domain specific code can not be written and
* intermediary id objects need to be managed.
* <li>Guice is relatively fast and we should not make things worse. We're trying our best to
* optimize synchronization for speed and memory. Happy path should be almost as fast as in a
* single threaded solution and should not take much more memory.
* <li>Error message generation in Guice was not meant to be used like this and to work around its
* APIs we need a lot of code. Additional complexity comes from inherent data races as message
* is only generated when failure occurs on proxy object generation. Things get ugly pretty
* fast.
* </ol>
*
* @see #scope(Key, Provider)
* @see CycleDetectingLock
* @author timofeyb (Timothy Basanov)
*/
public class SingletonScope implements Scope {
A sentinel value representing null. /** A sentinel value representing null. */
private static final Object NULL = new Object();
Allows us to detect when circular proxies are necessary. It's only used during singleton
instance initialization, after initialization direct access through volatile field is used.
NB: Factory uses Key
s as a user locks ids, different injectors can share them. Cycles are detected properly as cycle detection does not rely on user locks ids, but error message generated could be less than ideal.
/**
* Allows us to detect when circular proxies are necessary. It's only used during singleton
* instance initialization, after initialization direct access through volatile field is used.
*
* <p>NB: Factory uses {@link Key}s as a user locks ids, different injectors can share them.
* Cycles are detected properly as cycle detection does not rely on user locks ids, but error
* message generated could be less than ideal.
*/
// TODO(user): we may use one factory per injector tree for optimization reasons
private static final CycleDetectingLockFactory<Key<?>> cycleDetectingLockFactory =
new CycleDetectingLockFactory<Key<?>>();
Provides singleton scope with the following properties:
- creates no more than one instance per Key as a creator is used no more than once
- result is cached and returned quickly on subsequent calls
- exception in a creator is not treated as instance creation and is not cached
- creates singletons in parallel whenever possible
- waits for dependent singletons to be created even across threads and when dependencies
are shared as long as no circular dependencies are detected
- returns circular proxy only when circular dependencies are detected
- aside from that, blocking synchronization is only used for proxy creation and
initialization
See Also: - CycleDetectingLockFactory
/**
* Provides singleton scope with the following properties:
*
* <ul>
* <li>creates no more than one instance per Key as a creator is used no more than once
* <li>result is cached and returned quickly on subsequent calls
* <li>exception in a creator is not treated as instance creation and is not cached
* <li>creates singletons in parallel whenever possible
* <li>waits for dependent singletons to be created even across threads and when dependencies
* are shared as long as no circular dependencies are detected
* <li>returns circular proxy only when circular dependencies are detected
* <li>aside from that, blocking synchronization is only used for proxy creation and
* initialization
* </ul>
*
* @see CycleDetectingLockFactory
*/
@Override
public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
/** Locking strategy: */
return new Provider<T>() {
The lazily initialized singleton instance. Once set, this will either have type T or will
be equal to NULL. Would never be reset to null.
Locking strategy: double-checked locking for quick exit when scope is initialized.
/**
* The lazily initialized singleton instance. Once set, this will either have type T or will
* be equal to NULL. Would never be reset to null.
*
* <p>Locking strategy: double-checked locking for quick exit when scope is initialized.
*/
volatile Object instance;
Circular proxies are used when potential deadlocks are detected. Guarded by itself.
ConstructionContext is not thread-safe, so each call should be synchronized.
Locking strategy: manipulations with proxies list or instance initialization.
/**
* Circular proxies are used when potential deadlocks are detected. Guarded by itself.
* ConstructionContext is not thread-safe, so each call should be synchronized.
*
* <p>Locking strategy: manipulations with proxies list or instance initialization.
*/
final ConstructionContext<T> constructionContext = new ConstructionContext<>();
For each binding there is a separate lock that we hold during object creation.
Locking strategy: singleton instance creation.
- allows to guarantee only one instance per singleton,
- special type of a lock, that prevents potential deadlocks,
- guards constructionContext for all operations except proxy creation
/**
* For each binding there is a separate lock that we hold during object creation.
*
* <p>Locking strategy: singleton instance creation.
*
* <ul>
* <li>allows to guarantee only one instance per singleton,
* <li>special type of a lock, that prevents potential deadlocks,
* <li>guards constructionContext for all operations except proxy creation
* </ul>
*/
final CycleDetectingLock<Key<?>> creationLock = cycleDetectingLockFactory.create(key);
The singleton provider needs a reference back to the injector, in order to get ahold of
InternalContext during instantiation.
/**
* The singleton provider needs a reference back to the injector, in order to get ahold of
* InternalContext during instantiation.
*/
final /* @Nullable */ InjectorImpl injector;
{
// If we are getting called by Scoping
if (creator instanceof ProviderToInternalFactoryAdapter) {
injector = ((ProviderToInternalFactoryAdapter) creator).getInjector();
} else {
injector = null;
}
}
@SuppressWarnings("DoubleCheckedLocking")
@Override
public T get() {
// cache volatile variable for the usual case of already initialized object
final Object initialInstance = instance;
if (initialInstance == null) {
// instance is not initialized yet
// first, store the current InternalContext in a map, so that if there is a circular
// dependency error, we can use the InternalContext objects to create a complete
// error message.
// Handle injector being null, which can happen when users call Scoping.scope themselves
final InternalContext context = injector == null ? null : injector.getLocalContext();
// acquire lock for current binding to initialize an instance
final ListMultimap<Thread, Key<?>> locksCycle =
creationLock.lockOrDetectPotentialLocksCycle();
if (locksCycle.isEmpty()) {
// this thread now owns creation of an instance
try {
// intentionally reread volatile variable to prevent double initialization
if (instance == null) {
// creator throwing an exception can cause circular proxies created in
// different thread to never be resolved, just a warning
T provided = creator.get();
Object providedNotNull = provided == null ? NULL : provided;
// scope called recursively can initialize instance as a side effect
if (instance == null) {
// instance is still not initialized, so we can proceed
// don't remember proxies created by Guice on circular dependency
// detection within the same thread; they are not real instances to cache
if (Scopes.isCircularProxy(provided)) {
return provided;
}
synchronized (constructionContext) {
// guarantee thread-safety for instance and proxies initialization
instance = providedNotNull;
constructionContext.setProxyDelegates(provided);
}
} else {
// safety assert in case instance was initialized
Preconditions.checkState(
instance == providedNotNull,
"Singleton is called recursively returning different results");
}
}
} catch (RuntimeException e) {
// something went wrong, be sure to clean a construction context
// this helps to prevent potential memory leaks in circular proxies list
synchronized (constructionContext) {
constructionContext.finishConstruction();
}
throw e;
} finally {
// always release our creation lock, even on failures
creationLock.unlock();
}
} else {
if (context == null) {
throw new ProvisionException(
ImmutableList.of(createCycleDependenciesMessage(locksCycle, null)));
}
// potential deadlock detected, creation lock is not taken by this thread
synchronized (constructionContext) {
// guarantee thread-safety for instance and proxies initialization
if (instance == null) {
// creating a proxy to satisfy circular dependency across several threads
Dependency<?> dependency =
Preconditions.checkNotNull(
context.getDependency(), "internalContext.getDependency()");
Class<?> rawType = dependency.getKey().getTypeLiteral().getRawType();
try {
@SuppressWarnings("unchecked")
T proxy =
(T) constructionContext.createProxy(context.getInjectorOptions(), rawType);
return proxy;
} catch (InternalProvisionException e) {
// best effort to create a rich error message
Message proxyCreationError = Iterables.getOnlyElement(e.getErrors());
Message cycleDependenciesMessage =
createCycleDependenciesMessage(locksCycle, proxyCreationError);
// adding stack trace generated by us in addition to a standard one
throw new ProvisionException(
ImmutableList.of(cycleDependenciesMessage, proxyCreationError));
}
}
}
}
// at this point we're sure that singleton was initialized,
// reread volatile variable to catch all corner cases
// caching volatile variable to minimize number of reads performed
final Object initializedInstance = instance;
Preconditions.checkState(
initializedInstance != null,
"Internal error: Singleton is not initialized contrary to our expectations");
@SuppressWarnings("unchecked")
T initializedTypedInstance = (T) initializedInstance;
return initializedInstance == NULL ? null : initializedTypedInstance;
} else {
// singleton is already initialized and local cache can be used
@SuppressWarnings("unchecked")
T typedInitialIntance = (T) initialInstance;
return initialInstance == NULL ? null : typedInitialIntance;
}
}
Helper method to create beautiful and rich error descriptions. Best effort and slow. Tries
its best to provide dependency information from injectors currently available in a global
internal context.
The main thing being done is creating a list of Dependencies involved into lock cycle
across all the threads involved. This is a structure we're creating:
{ Current Thread, C.class, B.class, Other Thread, B.class, C.class, Current Thread }
To be inserted in the beginning by Guice: { A.class, B.class, C.class }
When we're calling Guice to create A and it fails in the deadlock while trying to create C,
which is being created by another thread, which waits for B. List would be reversed before
printing it to the end user.
/**
* Helper method to create beautiful and rich error descriptions. Best effort and slow. Tries
* its best to provide dependency information from injectors currently available in a global
* internal context.
*
* <p>The main thing being done is creating a list of Dependencies involved into lock cycle
* across all the threads involved. This is a structure we're creating:
*
* <pre>
* { Current Thread, C.class, B.class, Other Thread, B.class, C.class, Current Thread }
* To be inserted in the beginning by Guice: { A.class, B.class, C.class }
* </pre>
*
* When we're calling Guice to create A and it fails in the deadlock while trying to create C,
* which is being created by another thread, which waits for B. List would be reversed before
* printing it to the end user.
*/
private Message createCycleDependenciesMessage(
ListMultimap<Thread, Key<?>> locksCycle, /* @Nullable */ Message proxyCreationError) {
// this is the main thing that we'll show in an error message,
// current thread is populate by Guice
StringBuilder sb = new StringBuilder();
Formatter fmt = new Formatter(sb);
fmt.format("Encountered circular dependency spanning several threads.");
if (proxyCreationError != null) {
fmt.format(" %s", proxyCreationError.getMessage());
}
fmt.format("%n");
for (Thread lockedThread : locksCycle.keySet()) {
List<Key<?>> lockedKeys = locksCycle.get(lockedThread);
fmt.format("%s is holding locks the following singletons in the cycle:%n", lockedThread);
for (Key<?> lockedKey : lockedKeys) {
fmt.format("%s%n", Errors.convert(lockedKey));
}
for (StackTraceElement traceElement : lockedThread.getStackTrace()) {
fmt.format("\tat %s%n", traceElement);
}
}
fmt.close();
return new Message(Thread.currentThread(), sb.toString());
}
@Override
public String toString() {
return String.format("%s[%s]", creator, Scopes.SINGLETON);
}
};
}
@Override
public String toString() {
return "Scopes.SINGLETON";
}
}