diff options
author | Sam Berlin <sberlin@gmail.com> | 2015-04-21 14:14:37 -0400 |
---|---|---|
committer | Sam Berlin <sberlin@gmail.com> | 2015-04-21 14:14:37 -0400 |
commit | e3f556653ce41ff4e133ab45029c261db9ca35f4 (patch) | |
tree | bb18ba0ee3b54f497687c6dd8551e03b3688760d | |
parent | 06669e7978846489c0b80ca5a18f3cae0283f089 (diff) | |
parent | 3809a8196b693145828edd349952a9c4975ec249 (diff) | |
download | guice-e3f556653ce41ff4e133ab45029c261db9ca35f4.tar.gz |
Merge pull request #915 from google/moe_writing_branch_from_156c8cc762fab971efb727c7ab107fa243be2fc9
Merge internal changes
23 files changed, 1141 insertions, 209 deletions
diff --git a/core/src/com/google/inject/internal/BindingProcessor.java b/core/src/com/google/inject/internal/BindingProcessor.java index 8e83d18b..b3c869e3 100644 --- a/core/src/com/google/inject/internal/BindingProcessor.java +++ b/core/src/com/google/inject/internal/BindingProcessor.java @@ -111,7 +111,7 @@ final class BindingProcessor extends AbstractBindingProcessor { // always visited with Binding<T> @SuppressWarnings("unchecked") InternalFactory<T> factory = new InternalFactoryToInitializableAdapter<T>( - initializable, source, !injector.options.disableCircularProxies, + initializable, source, injector.provisionListenerStore.get((ProviderInstanceBinding<T>)binding)); InternalFactory<? extends T> scopedFactory = Scoping.scope(key, injector, factory, source, scoping); @@ -127,7 +127,7 @@ final class BindingProcessor extends AbstractBindingProcessor { // always visited with Binding<T> @SuppressWarnings("unchecked") BoundProviderFactory<T> boundProviderFactory = new BoundProviderFactory<T>( - injector, providerKey, source, !injector.options.disableCircularProxies, + injector, providerKey, source, injector.provisionListenerStore.get((ProviderKeyBinding<T>) binding)); bindingData.addCreationListener(boundProviderFactory); InternalFactory<? extends T> scopedFactory = Scoping.scope( diff --git a/core/src/com/google/inject/internal/BoundProviderFactory.java b/core/src/com/google/inject/internal/BoundProviderFactory.java index 27ae1aa4..9f523dfa 100644 --- a/core/src/com/google/inject/internal/BoundProviderFactory.java +++ b/core/src/com/google/inject/internal/BoundProviderFactory.java @@ -38,9 +38,8 @@ final class BoundProviderFactory<T> extends ProviderInternalFactory<T> implement InjectorImpl injector, Key<? extends javax.inject.Provider<? extends T>> providerKey, Object source, - boolean allowProxy, ProvisionListenerStackCallback<T> provisionCallback) { - super(source, allowProxy); + super(source); this.provisionCallback = checkNotNull(provisionCallback, "provisionCallback"); this.injector = injector; this.providerKey = providerKey; @@ -60,7 +59,7 @@ final class BoundProviderFactory<T> extends ProviderInternalFactory<T> implement try { errors = errors.withSource(providerKey); javax.inject.Provider<? extends T> provider = providerFactory.get(errors, context, dependency, true); - return circularGet(provider, errors, context, dependency, linked, provisionCallback); + return circularGet(provider, errors, context, dependency, provisionCallback); } finally { context.popState(); } diff --git a/core/src/com/google/inject/internal/ConstructionContext.java b/core/src/com/google/inject/internal/ConstructionContext.java index 8a5be013..ced388f6 100644 --- a/core/src/com/google/inject/internal/ConstructionContext.java +++ b/core/src/com/google/inject/internal/ConstructionContext.java @@ -16,6 +16,8 @@ package com.google.inject.internal; +import com.google.inject.internal.InjectorImpl.InjectorOptions; + import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; @@ -57,11 +59,11 @@ final class ConstructionContext<T> { invocationHandlers = null; } - public Object createProxy(Errors errors, Class<?> expectedType) throws ErrorsException { - // TODO: if I create a proxy which implements all the interfaces of - // the implementation type, I'll be able to get away with one proxy - // instance (as opposed to one per caller). - + public Object createProxy(Errors errors, InjectorOptions injectorOptions, + Class<?> expectedType) throws ErrorsException { + if (injectorOptions.disableCircularProxies) { + throw errors.circularProxiesDisabled(expectedType).toException(); + } if (!expectedType.isInterface()) { throw errors.cannotSatisfyCircularDependency(expectedType).toException(); } @@ -73,6 +75,9 @@ final class ConstructionContext<T> { DelegatingInvocationHandler<T> invocationHandler = new DelegatingInvocationHandler<T>(); invocationHandlers.add(invocationHandler); + // TODO: if I create a proxy which implements all the interfaces of + // the implementation type, I'll be able to get away with one proxy + // instance (as opposed to one per caller). ClassLoader classLoader = BytecodeGen.getClassLoader(expectedType); return expectedType.cast(Proxy.newProxyInstance(classLoader, new Class[] { expectedType, CircularDependencyProxy.class }, invocationHandler)); @@ -83,6 +88,8 @@ final class ConstructionContext<T> { for (DelegatingInvocationHandler<T> handler : invocationHandlers) { handler.setDelegate(delegate); } + // initialization of each handler can happen no more than once + invocationHandlers = null; } } } diff --git a/core/src/com/google/inject/internal/ConstructorBindingImpl.java b/core/src/com/google/inject/internal/ConstructorBindingImpl.java index 1e8bf78f..8fb21035 100644 --- a/core/src/com/google/inject/internal/ConstructorBindingImpl.java +++ b/core/src/com/google/inject/internal/ConstructorBindingImpl.java @@ -133,7 +133,6 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T> @SuppressWarnings("unchecked") // the result type always agrees with the ConstructorInjector type public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { - factory.allowCircularProxy = !injector.options.disableCircularProxies; factory.constructorInjector = (ConstructorInjector<T>) injector.constructors.get(constructorInjectionPoint, errors); factory.provisionCallback = @@ -246,7 +245,6 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T> private static class Factory<T> implements InternalFactory<T> { private final boolean failIfNotLinked; private final Key<?> key; - private boolean allowCircularProxy; private ConstructorInjector<T> constructorInjector; private ProvisionListenerStackCallback<T> provisionCallback; @@ -267,7 +265,7 @@ final class ConstructorBindingImpl<T> extends BindingImpl<T> // This may not actually be safe because it could return a super type of T (if that's all the // client needs), but it should be OK in practice thanks to the wonders of erasure. return (T) constructorInjector.construct(errors, context, - dependency.getKey().getTypeLiteral().getRawType(), allowCircularProxy, provisionCallback); + dependency.getKey().getTypeLiteral().getRawType(), provisionCallback); } } } diff --git a/core/src/com/google/inject/internal/ConstructorInjector.java b/core/src/com/google/inject/internal/ConstructorInjector.java index ca7bb277..1ff4be1f 100644 --- a/core/src/com/google/inject/internal/ConstructorInjector.java +++ b/core/src/com/google/inject/internal/ConstructorInjector.java @@ -59,19 +59,16 @@ final class ConstructorInjector<T> { * it may return a proxy. */ Object construct(final Errors errors, final InternalContext context, - Class<?> expectedType, boolean allowProxy, + Class<?> expectedType, ProvisionListenerStackCallback<T> provisionCallback) throws ErrorsException { final ConstructionContext<T> constructionContext = context.getConstructionContext(this); // We have a circular reference between constructors. Return a proxy. if (constructionContext.isConstructing()) { - if(!allowProxy) { - throw errors.circularProxiesDisabled(expectedType).toException(); - } else { - // TODO (crazybob): if we can't proxy this object, can we proxy the other object? - return constructionContext.createProxy(errors, expectedType); - } + // TODO (crazybob): if we can't proxy this object, can we proxy the other object? + return constructionContext.createProxy( + errors, context.getInjectorOptions(), expectedType); } // If we're re-entering this factory while injecting fields or methods, @@ -85,7 +82,7 @@ final class ConstructorInjector<T> { try { // Optimization: Don't go through the callback stack if we have no listeners. if (!provisionCallback.hasListeners()) { - return provision(errors, context, constructionContext); + return provision(errors, context, constructionContext); } else { return provisionCallback.provision(errors, context, new ProvisionCallback<T>() { public T call() throws ErrorsException { diff --git a/core/src/com/google/inject/internal/CycleDetectingLock.java b/core/src/com/google/inject/internal/CycleDetectingLock.java new file mode 100644 index 00000000..9c46c44e --- /dev/null +++ b/core/src/com/google/inject/internal/CycleDetectingLock.java @@ -0,0 +1,292 @@ +package com.google.inject.internal; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Simplified version of {@link Lock} that is special due to how it handles deadlocks detection. + * + * <p>Is an inherent part of {@link SingletonScope}, moved into a upper level class due + * to its size and complexity. + * + * @param <ID> Lock identification provided by the client, is returned unmodified to the client + * when lock cycle is detected to identify it. Only toString() needs to be implemented. + * Lock references this object internally, + * for the purposes of Garbage Collection you should not use heavy IDs. + * Lock is referenced by a lock factory as long as it's owned by a thread. + * + * @see SingletonScope + * @see com.google.inject.internal.CycleDetectingLock.CycleDetectingLockFactory + * + * @author timofeyb (Timothy Basanov) + */ +interface CycleDetectingLock<ID> { + + /** + * Takes a lock in a blocking fashion in case no potential deadlocks are detected. + * If the lock was successfully owned, returns an empty map indicating no detected potential + * deadlocks. + * + * Otherwise, a map indicating threads involved in a potential deadlock are returned. + * Map is ordered by dependency cycle and lists locks for each thread that are part of + * the loop in order. Returned map is created atomically. + * + * In case no cycle is detected performance is O(threads creating singletons), + * in case cycle is detected performance is O(singleton locks). + */ + ListMultimap<Long, ID> lockOrDetectPotentialLocksCycle(); + + /** + * Unlocks previously locked lock. + */ + void unlock(); + + /** + * Wraps locks so they would never cause a deadlock. On each + * {@link CycleDetectingLock#lockOrDetectPotentialLocksCycle} we check for dependency cycles + * within locks created by the same factory. Either we detect a cycle and return it + * or take it atomically. + * + * <p>Important to note that we do not prevent deadlocks in the client code. As an example: + * Thread A takes lock L and creates singleton class CA depending on the singleton class CB. + * Meanwhile thread B is creating class CB and is waiting on the lock L. Issue happens + * due to client code creating interdependent classes and using locks, where + * no guarantees on the creation order from Guice are provided. + * + * <p>Instances of these locks are not intended to be exposed outside of {@link SingletonScope}. + */ + class CycleDetectingLockFactory<ID> { + + /** + * Specifies lock that thread is currently waiting on to own it. + * Used only for purposes of locks cycle detection. + * + * Key: thread id + * Value: lock that is being waited on + * + * Element is added inside {@link #lockOrDetectPotentialLocksCycle()} before {@link Lock#lock} + * is called. Element is removed inside {@link #lockOrDetectPotentialLocksCycle()} after + * {@link Lock#lock} and synchronously with adding it to {@link #locksOwnedByThread}. + * + * Same lock can be added for several threads in case all of them are trying to + * take it. + * + * Guarded by {@code this}. + */ + private Map<Long, ReentrantCycleDetectingLock> lockThreadIsWaitingOn = Maps.newHashMap(); + + /** + * Lists locks that thread owns. + * Used only to populate locks in a potential cycle when it is detected. + * + * Key: thread id + * Value: stack of locks that were owned. + * + * Element is added inside {@link #lockOrDetectPotentialLocksCycle()} after {@link Lock#lock} + * is called. Element is removed inside {@link #unlock()} synchronously with + * {@link Lock#unlock()} call. + * + * Same lock can only be present several times for the same thread as locks are + * reentrant. Lock can not be owned by several different threads as the same time. + * + * Guarded by {@code this}. + */ + private final Multimap<Long, ReentrantCycleDetectingLock> locksOwnedByThread = + MultimapBuilder.linkedHashKeys().linkedHashSetValues().build(); + + /** + * Creates new lock within this factory context. We can guarantee that locks created by + * the same factory would not deadlock. + * + * @param newLockId lock id that would be used to report lock cycles if detected + */ + CycleDetectingLock<ID> create(ID newLockId) { + return new ReentrantCycleDetectingLock(newLockId, new ReentrantLock()); + } + + /** The implementation for {@link CycleDetectingLock}. */ + class ReentrantCycleDetectingLock implements CycleDetectingLock<ID> { + + /** Underlying lock used for actual waiting when no potential deadlocks are detected. */ + private final Lock lockImplementation; + /** User id for this lock. */ + private final ID userLockId; + /** + * Thread id for the thread that owned this lock. Nullable. + * Guarded by {@code CycleDetectingLockFactory.this}. + */ + private Long lockOwnerThreadId = null; + /** + * Number of times that thread owned this lock. + * Guarded by {@code CycleDetectingLockFactory.this}. + */ + private int lockReentranceCount = 0; + + ReentrantCycleDetectingLock(ID userLockId, Lock lockImplementation) { + this.userLockId = Preconditions.checkNotNull(userLockId, "userLockId"); + this.lockImplementation = Preconditions.checkNotNull( + lockImplementation, "lockImplementation"); + } + + @Override public ListMultimap<Long, ID> lockOrDetectPotentialLocksCycle() { + final long currentThreadId = Thread.currentThread().getId(); + synchronized (CycleDetectingLockFactory.this) { + checkState(); + ListMultimap<Long, ID> locksInCycle = detectPotentialLocksCycle(); + if (!locksInCycle.isEmpty()) { + // potential deadlock is found, we don't try to take this lock + return locksInCycle; + } + + lockThreadIsWaitingOn.put(currentThreadId, this); + } + + // this may be blocking, but we don't expect it to cause a deadlock + lockImplementation.lock(); + + synchronized (CycleDetectingLockFactory.this) { + // current thread is no longer waiting on this lock + lockThreadIsWaitingOn.remove(currentThreadId); + checkState(); + + // mark it as owned by us + lockOwnerThreadId = currentThreadId; + lockReentranceCount++; + // add this lock to the list of locks owned by a current thread + locksOwnedByThread.put(currentThreadId, this); + } + // no deadlock is found, locking successful + return ImmutableListMultimap.of(); + } + + @Override public void unlock() { + final long currentThreadId = Thread.currentThread().getId(); + synchronized (CycleDetectingLockFactory.this) { + checkState(); + Preconditions.checkState(lockOwnerThreadId != null, + "Thread is trying to unlock a lock that is not locked"); + Preconditions.checkState(lockOwnerThreadId == currentThreadId, + "Thread is trying to unlock a lock owned by another thread"); + + // releasing underlying lock + lockImplementation.unlock(); + + // be sure to release the lock synchronously with updating internal state + lockReentranceCount--; + if (lockReentranceCount == 0) { + // we no longer own this lock + lockOwnerThreadId = null; + Preconditions.checkState(locksOwnedByThread.remove(currentThreadId, this), + "Internal error: Can not find this lock in locks owned by a current thread"); + if (locksOwnedByThread.get(currentThreadId).isEmpty()) { + // clearing memory + locksOwnedByThread.removeAll(currentThreadId); + } + } + } + } + + /** Check consistency of an internal state. */ + void checkState() throws IllegalStateException { + final long currentThreadId = Thread.currentThread().getId(); + Preconditions.checkState(!lockThreadIsWaitingOn.containsKey(currentThreadId), + "Internal error: Thread should not be in a waiting thread on a lock now"); + if (lockOwnerThreadId != null) { + // check state of a locked lock + Preconditions.checkState(lockReentranceCount >= 0, + "Internal error: Lock ownership and reentrance count internal states do not match"); + Preconditions.checkState(locksOwnedByThread.get(lockOwnerThreadId).contains(this), + "Internal error: Set of locks owned by a current thread and lock " + + "ownership status do not match"); + } else { + // check state of a non locked lock + Preconditions.checkState(lockReentranceCount == 0, + "Internal error: Reentrance count of a non locked lock is expect to be zero"); + Preconditions.checkState(!locksOwnedByThread.values().contains(this), + "Internal error: Non locked lock should not be owned by any thread"); + } + } + + /** + * Algorithm to detect a potential lock cycle. + * + * For lock's thread owner check which lock is it trying to take. + * Repeat recursively. When current thread is found a potential cycle is detected. + * + * @see CycleDetectingLock#lockOrDetectPotentialLocksCycle() + */ + private ListMultimap<Long, ID> detectPotentialLocksCycle() { + final long currentThreadId = Thread.currentThread().getId(); + if (lockOwnerThreadId == null || lockOwnerThreadId == currentThreadId) { + // if nobody owns this lock, lock cycle is impossible + // if a current thread owns this lock, we let Guice to handle it + return ImmutableListMultimap.of(); + } + + ListMultimap<Long, ID> potentialLocksCycle = + MultimapBuilder.linkedHashKeys().arrayListValues().build(); + // lock that is a part of a potential locks cycle, starts with current lock + ReentrantCycleDetectingLock lockOwnerWaitingOn = this; + // try to find a dependency path between lock's owner thread and a current thread + while (lockOwnerWaitingOn != null && lockOwnerWaitingOn.lockOwnerThreadId != null) { + Long threadOwnerThreadWaits = lockOwnerWaitingOn.lockOwnerThreadId; + // in case locks cycle exists lock we're waiting for is part of it + potentialLocksCycle.putAll(threadOwnerThreadWaits, + getAllLockIdsAfter(threadOwnerThreadWaits, lockOwnerWaitingOn)); + + if (threadOwnerThreadWaits == currentThreadId) { + // owner thread depends on current thread, cycle detected + return potentialLocksCycle; + } + // going for the next thread we wait on indirectly + lockOwnerWaitingOn = lockThreadIsWaitingOn.get(threadOwnerThreadWaits); + } + // no dependency path from an owner thread to a current thread + return ImmutableListMultimap.of(); + } + + /** Return locks owned by a thread after a lock specified, inclusive. */ + private List<ID> getAllLockIdsAfter(long threadId, ReentrantCycleDetectingLock lock) { + List<ID> ids = Lists.newArrayList(); + boolean found = false; + Collection<ReentrantCycleDetectingLock> ownedLocks = locksOwnedByThread.get(threadId); + Preconditions.checkNotNull(ownedLocks, + "Internal error: No locks were found taken by a thread"); + for (ReentrantCycleDetectingLock ownedLock : ownedLocks) { + if (ownedLock == lock) { + found = true; + } + if (found) { + ids.add(ownedLock.userLockId); + } + } + Preconditions.checkState(found, "Internal error: We can not find locks that " + + "created a cycle that we detected"); + return ids; + } + + @Override public String toString() { + // copy is made to prevent a data race + // no synchronization is used, potentially stale data, should be good enough + Long localLockOwnerThreadId = this.lockOwnerThreadId; + if (localLockOwnerThreadId != null) { + return String.format("CycleDetectingLock[%s][locked by %s]", + userLockId, localLockOwnerThreadId); + } else { + return String.format("CycleDetectingLock[%s][unlocked]", userLockId); + } + } + } + } +} diff --git a/core/src/com/google/inject/internal/DelegatingInvocationHandler.java b/core/src/com/google/inject/internal/DelegatingInvocationHandler.java index a7b042b7..32441ff6 100644 --- a/core/src/com/google/inject/internal/DelegatingInvocationHandler.java +++ b/core/src/com/google/inject/internal/DelegatingInvocationHandler.java @@ -16,24 +16,34 @@ package com.google.inject.internal; + +import com.google.common.base.Preconditions; + import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; class DelegatingInvocationHandler<T> implements InvocationHandler { + private volatile boolean initialized; + private T delegate; public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (delegate == null) { - throw new IllegalStateException("This is a proxy used to support" - + " circular references. The object we're" - + " proxying is not constructed yet. Please wait until after" - + " injection has completed to use this object."); - } - try { + // checking volatile field for synchronization + Preconditions.checkState(initialized, + "This is a proxy used to support" + + " circular references. The object we're" + + " proxying is not constructed yet. Please wait until after" + + " injection has completed to use this object."); + Preconditions.checkNotNull(delegate, + "This is a proxy used to support" + + " circular references. The object we're " + + " proxying is initialized to null." + + " No methods can be called."); + // TODO: method.setAccessible(true); ? // this would fix visibility errors when we proxy a // non-public interface. @@ -47,11 +57,8 @@ class DelegatingInvocationHandler<T> implements InvocationHandler { } } - public T getDelegate() { - return delegate; - } - void setDelegate(T delegate) { this.delegate = delegate; + initialized = true; } } diff --git a/core/src/com/google/inject/internal/Errors.java b/core/src/com/google/inject/internal/Errors.java index 5ce3b429..7527e2a1 100644 --- a/core/src/com/google/inject/internal/Errors.java +++ b/core/src/com/google/inject/internal/Errors.java @@ -812,6 +812,9 @@ public final class Errors implements Serializable { Key<?> key = (Key<?>) source; formatter.format(" while locating %s%n", convert(key, elementSource)); + } else if (source instanceof Thread) { + formatter.format(" in thread %s%n", source); + } else { formatter.format(" at %s%s%n", source, modules); } diff --git a/core/src/com/google/inject/internal/InheritingState.java b/core/src/com/google/inject/internal/InheritingState.java index db6a7a67..18363f48 100644 --- a/core/src/com/google/inject/internal/InheritingState.java +++ b/core/src/com/google/inject/internal/InheritingState.java @@ -60,13 +60,10 @@ final class InheritingState implements State { private final List<ModuleAnnotatedMethodScannerBinding> scannerBindings = Lists.newArrayList(); private final WeakKeySet blacklistedKeys; private final Object lock; - private final Object singletonCreationLock; InheritingState(State parent) { this.parent = checkNotNull(parent, "parent"); this.lock = (parent == State.NONE) ? this : parent.lock(); - this.singletonCreationLock = - (parent == State.NONE) ? new Object() : parent.singletonCreationLock(); this.blacklistedKeys = new WeakKeySet(lock); } @@ -190,10 +187,6 @@ final class InheritingState implements State { return lock; } - public Object singletonCreationLock() { - return singletonCreationLock; - } - public Map<Class<? extends Annotation>, Scope> getScopes() { ImmutableMap.Builder<Class<? extends Annotation>, Scope> builder = ImmutableMap.builder(); for (Map.Entry<Class<? extends Annotation>, ScopeBinding> entry : scopes.entrySet()) { diff --git a/core/src/com/google/inject/internal/InjectorImpl.java b/core/src/com/google/inject/internal/InjectorImpl.java index 3d868eba..54ce8a3c 100644 --- a/core/src/com/google/inject/internal/InjectorImpl.java +++ b/core/src/com/google/inject/internal/InjectorImpl.java @@ -57,6 +57,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentMap; /** * Default {@link Injector} implementation. @@ -709,8 +710,7 @@ final class InjectorImpl implements Injector, Lookups { @SuppressWarnings("unchecked") Key<? extends Provider<T>> providerKey = (Key<? extends Provider<T>>) Key.get(providerType); ProvidedByInternalFactory<T> internalFactory = - new ProvidedByInternalFactory<T>(rawType, providerType, - providerKey, !options.disableCircularProxies); + new ProvidedByInternalFactory<T>(rawType, providerType, providerKey); Object source = rawType; BindingImpl<T> binding = LinkedProviderBindingImpl.createWithInitializer( this, @@ -1051,7 +1051,31 @@ final class InjectorImpl implements Injector, Lookups { return getProvider(type).get(); } + /** @see #getGlobalInternalContext */ private final ThreadLocal<Object[]> localContext; + /** + * Synchronization: map value is modified only for the current thread, + * it's ok to read map values of other threads. It can change between your + * calls. + * + * @see #getGlobalInternalContext + */ + private static final ConcurrentMap<Thread, InternalContext> globalInternalContext = + Maps.newConcurrentMap(); + + /** + * Provides access to the internal context for the current injector of all threads. + * One does not need to use this from Guice source code as context could be passed on the stack. + * It is required for custom scopes which are called from Guice and sometimes do require + * access to current internal context, but it is not passed in. Contrary to {@link #localContext} + * it is not used to store injector-specific state, but to provide easy access to the current + * state. + * + * @return unmodifiable map + */ + static Map<Thread, InternalContext> getGlobalInternalContext() { + return Collections.unmodifiableMap(globalInternalContext); + } /** Looks up thread local context. Creates (and removes) a new context if necessary. */ <T> T callInContext(ContextualCallable<T> callable) throws ErrorsException { @@ -1060,17 +1084,30 @@ final class InjectorImpl implements Injector, Lookups { reference = new Object[1]; localContext.set(reference); } + Thread currentThread = Thread.currentThread(); if (reference[0] == null) { - reference[0] = new InternalContext(); + reference[0] = new InternalContext(options); + globalInternalContext.put(currentThread, (InternalContext) reference[0]); try { - return callable.call((InternalContext)reference[0]); + return callable.call((InternalContext) reference[0]); } finally { - // Only clear the context if this call created it. + // Only clear contexts if this call created them. reference[0] = null; + globalInternalContext.remove(currentThread); } } else { - // Someone else will clean up this context. - return callable.call((InternalContext)reference[0]); + Object previousGlobalInternalContext = globalInternalContext.get(currentThread); + globalInternalContext.put(currentThread, (InternalContext) reference[0]); + try { + // Someone else will clean up this local context. + return callable.call((InternalContext) reference[0]); + } finally { + if (previousGlobalInternalContext != null) { + globalInternalContext.put(currentThread, (InternalContext) previousGlobalInternalContext); + } else { + globalInternalContext.remove(currentThread); + } + } } } diff --git a/core/src/com/google/inject/internal/InternalContext.java b/core/src/com/google/inject/internal/InternalContext.java index 1493c37d..0af19697 100644 --- a/core/src/com/google/inject/internal/InternalContext.java +++ b/core/src/com/google/inject/internal/InternalContext.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.inject.Key; +import com.google.inject.internal.InjectorImpl.InjectorOptions; import com.google.inject.spi.Dependency; import com.google.inject.spi.DependencyAndSource; @@ -35,6 +36,8 @@ import java.util.Map; */ final class InternalContext { + private final InjectorOptions options; + private Map<Object, ConstructionContext<?>> constructionContexts = Maps.newHashMap(); /** Keeps track of the type that is currently being requested for injection. */ @@ -43,6 +46,14 @@ final class InternalContext { /** Keeps track of the hierarchy of types needed during injection. */ private final DependencyStack state = new DependencyStack(); + InternalContext(InjectorOptions options) { + this.options = options; + } + + public InjectorOptions getInjectorOptions() { + return options; + } + @SuppressWarnings("unchecked") public <T> ConstructionContext<T> getConstructionContext(Object key) { ConstructionContext<T> constructionContext @@ -65,13 +76,13 @@ final class InternalContext { state.add(dependency, source); return previous; } - + /** Pops the current state & sets the new dependency. */ public void popStateAndSetDependency(Dependency<?> newDependency) { state.pop(); this.dependency = newDependency; } - + /** Adds to the state without setting the dependency. */ public void pushState(Key<?> key, Object source) { state.add(key, source); @@ -81,7 +92,7 @@ final class InternalContext { public void popState() { state.pop(); } - + /** Returns the current dependency chain (all the state). */ public List<DependencyAndSource> getDependencyChain() { ImmutableList.Builder<DependencyAndSource> builder = ImmutableList.builder(); diff --git a/core/src/com/google/inject/internal/InternalFactoryToInitializableAdapter.java b/core/src/com/google/inject/internal/InternalFactoryToInitializableAdapter.java index b534f897..c02c70ea 100644 --- a/core/src/com/google/inject/internal/InternalFactoryToInitializableAdapter.java +++ b/core/src/com/google/inject/internal/InternalFactoryToInitializableAdapter.java @@ -34,16 +34,15 @@ final class InternalFactoryToInitializableAdapter<T> extends ProviderInternalFac public InternalFactoryToInitializableAdapter( Initializable<? extends javax.inject.Provider<? extends T>> initializable, - Object source, boolean allowProxy, - ProvisionListenerStackCallback<T> provisionCallback) { - super(source, allowProxy); + Object source, ProvisionListenerStackCallback<T> provisionCallback) { + super(source); this.provisionCallback = checkNotNull(provisionCallback, "provisionCallback"); this.initializable = checkNotNull(initializable, "provider"); } public T get(Errors errors, InternalContext context, Dependency<?> dependency, boolean linked) throws ErrorsException { - return circularGet(initializable.get(errors), errors, context, dependency, linked, + return circularGet(initializable.get(errors), errors, context, dependency, provisionCallback); } diff --git a/core/src/com/google/inject/internal/ProvidedByInternalFactory.java b/core/src/com/google/inject/internal/ProvidedByInternalFactory.java index 0a0191db..1591c508 100644 --- a/core/src/com/google/inject/internal/ProvidedByInternalFactory.java +++ b/core/src/com/google/inject/internal/ProvidedByInternalFactory.java @@ -42,9 +42,8 @@ class ProvidedByInternalFactory<T> extends ProviderInternalFactory<T> ProvidedByInternalFactory( Class<?> rawType, Class<? extends Provider<?>> providerType, - Key<? extends Provider<T>> providerKey, - boolean allowProxy) { - super(providerKey, allowProxy); + Key<? extends Provider<T>> providerKey) { + super(providerKey); this.rawType = rawType; this.providerType = providerType; this.providerKey = providerKey; @@ -68,7 +67,7 @@ class ProvidedByInternalFactory<T> extends ProviderInternalFactory<T> errors = errors.withSource(providerKey); Provider<? extends T> provider = providerBinding.getInternalFactory().get( errors, context, dependency, true); - return circularGet(provider, errors, context, dependency, linked, provisionCallback); + return circularGet(provider, errors, context, dependency, provisionCallback); } finally { context.popState(); } diff --git a/core/src/com/google/inject/internal/ProviderInternalFactory.java b/core/src/com/google/inject/internal/ProviderInternalFactory.java index e0e7d1df..8574535d 100644 --- a/core/src/com/google/inject/internal/ProviderInternalFactory.java +++ b/core/src/com/google/inject/internal/ProviderInternalFactory.java @@ -32,31 +32,26 @@ import javax.inject.Provider; */ abstract class ProviderInternalFactory<T> implements InternalFactory<T> { - private final boolean allowProxy; protected final Object source; - ProviderInternalFactory(Object source, boolean allowProxy) { + ProviderInternalFactory(Object source) { this.source = checkNotNull(source, "source"); - this.allowProxy = allowProxy; } protected T circularGet(final Provider<? extends T> provider, final Errors errors, - InternalContext context, final Dependency<?> dependency, boolean linked, + InternalContext context, final Dependency<?> dependency, ProvisionListenerStackCallback<T> provisionCallback) throws ErrorsException { - Class<?> expectedType = dependency.getKey().getTypeLiteral().getRawType(); final ConstructionContext<T> constructionContext = context.getConstructionContext(this); // We have a circular reference between constructors. Return a proxy. if (constructionContext.isConstructing()) { - if (!allowProxy) { - throw errors.circularProxiesDisabled(expectedType).toException(); - } else { + Class<?> expectedType = dependency.getKey().getTypeLiteral().getRawType(); // TODO: if we can't proxy this object, can we proxy the other object? @SuppressWarnings("unchecked") - T proxyType = (T) constructionContext.createProxy(errors, expectedType); + T proxyType = (T) constructionContext.createProxy( + errors, context.getInjectorOptions(), expectedType); return proxyType; - } } // Optimization: Don't go through the callback stack if no one's listening. diff --git a/core/src/com/google/inject/internal/Scoping.java b/core/src/com/google/inject/internal/Scoping.java index 10afcba3..334aaef4 100644 --- a/core/src/com/google/inject/internal/Scoping.java +++ b/core/src/com/google/inject/internal/Scoping.java @@ -239,14 +239,9 @@ public abstract class Scoping { Scope scope = scoping.getScopeInstance(); - try { - SingletonScope.singletonCreationPerRootInjectorLock.set(injector.state.singletonCreationLock()); - Provider<T> scoped - = scope.scope(key, new ProviderToInternalFactoryAdapter<T>(injector, creator)); - return new InternalFactoryToProviderAdapter<T>(scoped, source); - } finally { - SingletonScope.singletonCreationPerRootInjectorLock.set(null); - } + Provider<T> scoped + = scope.scope(key, new ProviderToInternalFactoryAdapter<T>(injector, creator)); + return new InternalFactoryToProviderAdapter<T>(scoped, source); } /** diff --git a/core/src/com/google/inject/internal/SingletonScope.java b/core/src/com/google/inject/internal/SingletonScope.java index e98a0303..fe6287aa 100644 --- a/core/src/com/google/inject/internal/SingletonScope.java +++ b/core/src/com/google/inject/internal/SingletonScope.java @@ -1,16 +1,73 @@ package com.google.inject.internal; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.inject.Injector; import com.google.inject.Key; -import com.google.inject.OutOfScopeException; 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.DependencyAndSource; +import com.google.inject.spi.Message; + +import java.util.Collections; +import java.util.List; +import java.util.Map; /** * One instance per {@link Injector}. Also see {@code @}{@link 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. + * 1) 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. + * 2) 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. + * 3) 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. + * 4) 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. + * 5) 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. + * 6) 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. + * 7) 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. + * + * @see #scope(Key, Provider) + * @see CycleDetectingLock + * + * @author timofeyb (Timothy Basanov) */ public class SingletonScope implements Scope { @@ -18,70 +75,259 @@ public class SingletonScope implements Scope { private static final Object NULL = new Object(); /** - * Lock to use for new instances creation. This allows a per-root-Injector singleton lock, - * instead of a global lock across the JVM. Is set only during call to {@link #scope}. + * 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. * - * This is necessary because users have coded to a single {@link Scopes#SINGLETON} instance, - * and we cannot change that. Additionally, we can't reference the injector from a Key or - * Provider (the only variables available to the {@link #scope} method). Therefore, we rely - * on the injector implementation to explicitly set/unset the lock surrounding - * creation of the Provider the scope creates. + * 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. * - * @see {@link Scoping#scope(Key, InjectorImpl, InternalFactory, Object, Scoping)} for details. + * TODO(user): we may use one factory per injector tree for optimization reasons */ - static final ThreadLocal<Object> singletonCreationPerRootInjectorLock = - new ThreadLocal<Object>(); + 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 CycleDetectingLockFactory + */ public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) { - // lock is referenced from anonymous class instance - final Object rootInjectorLock = singletonCreationPerRootInjectorLock.get(); - if (rootInjectorLock == null) { - throw new OutOfScopeException("Singleton scope should only be used from Injector"); - } + /** + * Locking strategy: + * - volatile instance: double-checked locking for quick exit when scope is initialized, + * - constructionContext: manipulations with proxies list or instance initialization + * - creationLock: 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 + */ return new Provider<T>() { - /* + /** * The lazily initialized singleton instance. Once set, this will either have type T or will - * be equal to NULL. + * be equal to NULL. Would never be reset to null. + */ + 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. */ - private volatile Object instance; + final ConstructionContext<T> constructionContext = new ConstructionContext<T>(); + + /** For each binding there is a separate lock that we hold during object creation. */ + final CycleDetectingLock<Key<?>> creationLock = cycleDetectingLockFactory.create(key); - // DCL on a volatile is safe as of Java 5, which we obviously require. @SuppressWarnings("DoubleCheckedLocking") public T get() { - if (instance == null) { - /* - * Use a pretty coarse lock. We don't want to run into deadlocks - * when two threads try to load circularly-dependent objects. - * Maybe one of these days we will identify independent graphs of - * objects and offer to load them in parallel. - * - * This block is re-entrant for circular dependencies. - */ - synchronized (rootInjectorLock) { - if (instance == null) { - T provided = creator.get(); - - // don't remember proxies; these exist only to serve circular dependencies - if (Scopes.isCircularProxy(provided)) { - return provided; - } + // cache volatile variable for the usual case of already initialized object + final Object initialInstance = instance; + if (initialInstance == null) { + // instance is not initialized yet + + // acquire lock for current binding to initialize an instance + final ListMultimap<Long, 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; - Object providedOrSentinel = (provided == null) ? NULL : provided; - if (instance != null && instance != providedOrSentinel) { - throw new ProvisionException( - "Provider was reentrant while creating a singleton"); + // scope called recursively can initialize instance as a side effect + if (instance == null) { + // instance is still not initialized, se 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 { + // potential deadlock detected, creation lock is not taken by this thread + synchronized (constructionContext) { + // guarantee thread-safety for instance and proxies initialization + if (instance == null) { + // InjectorImpl.callInContext() sets this context when scope is called from Guice + Map<Thread, InternalContext> globalInternalContext = + InjectorImpl.getGlobalInternalContext(); + InternalContext internalContext = globalInternalContext.get(Thread.currentThread()); - instance = providedOrSentinel; + // creating a proxy to satisfy circular dependency across several threads + Dependency<?> dependency = Preconditions.checkNotNull( + internalContext.getDependency(), + "globalInternalContext.get(currentThread()).getDependency()"); + Class<?> rawType = dependency.getKey().getTypeLiteral().getRawType(); + + try { + @SuppressWarnings("unchecked") + T proxy = (T) constructionContext.createProxy( + new Errors(), internalContext.getInjectorOptions(), rawType); + return proxy; + } catch (ErrorsException e) { + // best effort to create a rich error message + List<Message> exceptionErrorMessages = e.getErrors().getMessages(); + // we expect an error thrown + Preconditions.checkState(exceptionErrorMessages.size() == 1); + // explicitly copy the map to guarantee iteration correctness + // it's ok to have a data race with other threads that are locked + Message cycleDependenciesMessage = createCycleDependenciesMessage( + ImmutableMap.copyOf(globalInternalContext), + locksCycle, + exceptionErrorMessages.get(0)); + // adding stack trace generated by us in addition to a standard one + throw new ProvisionException(ImmutableList.of( + cycleDependenciesMessage, exceptionErrorMessages.get(0))); + } + } } } + // 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; } + } - Object localInstance = instance; - // This is safe because instance has type T or is equal to NULL - @SuppressWarnings("unchecked") - T returnedInstance = (localInstance != NULL) ? (T) localInstance : null; - return returnedInstance; + /** + * 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( + Map<Thread, InternalContext> globalInternalContext, + ListMultimap<Long, Key<?>> locksCycle, + Message proxyCreationError) { + // this is the main thing that we'll show in an error message, + // current thread is populate by Guice + List<Object> sourcesCycle = Lists.newArrayList(); + sourcesCycle.add(Thread.currentThread()); + // temp map to speed up look ups + Map<Long, Thread> threadById = Maps.newHashMap(); + for (Thread thread : globalInternalContext.keySet()) { + threadById.put(thread.getId(), thread); + } + for (long lockedThreadId : locksCycle.keySet()) { + Thread lockedThread = threadById.get(lockedThreadId); + List<Key<?>> lockedKeys = Collections.unmodifiableList(locksCycle.get(lockedThreadId)); + if (lockedThread == null) { + // thread in a lock cycle is already terminated + continue; + } + List<DependencyAndSource> dependencyChain = null; + boolean allLockedKeysAreFoundInDependencies = false; + // thread in a cycle is still present + InternalContext lockedThreadInternalContext = globalInternalContext.get(lockedThread); + if (lockedThreadInternalContext != null) { + dependencyChain = lockedThreadInternalContext.getDependencyChain(); + + // check that all of the keys are still present in dependency chain in order + List<Key<?>> lockedKeysToFind = Lists.newLinkedList(lockedKeys); + // check stack trace of the thread + for (DependencyAndSource d : dependencyChain) { + Dependency<?> dependency = d.getDependency(); + if (dependency == null) { + continue; + } + if (dependency.getKey().equals(lockedKeysToFind.get(0))) { + lockedKeysToFind.remove(0); + if (lockedKeysToFind.isEmpty()) { + // everything is found! + allLockedKeysAreFoundInDependencies = true; + break; + } + } + } + } + if (allLockedKeysAreFoundInDependencies) { + // all keys are present in a dependency chain of a thread's last injector, + // highly likely that we just have discovered a dependency + // chain that is part of a lock cycle starting with the first lock owned + Key<?> firstLockedKey = lockedKeys.get(0); + boolean firstLockedKeyFound = false; + for (DependencyAndSource d : dependencyChain) { + Dependency<?> dependency = d.getDependency(); + if (dependency == null) { + continue; + } + if (firstLockedKeyFound) { + sourcesCycle.add(dependency); + sourcesCycle.add(d.getBindingSource()); + } else if (dependency.getKey().equals(firstLockedKey)) { + firstLockedKeyFound = true; + // for the very first one found we don't care why, so no dependency is added + sourcesCycle.add(d.getBindingSource()); + } + } + } else { + // something went wrong and not all keys are present in a state of an injector + // that was used last for a current thread. + // let's add all keys we're aware of, still better than nothing + sourcesCycle.addAll(lockedKeys); + } + // mentions that a tread is a part of a cycle + sourcesCycle.add(lockedThread); + } + return new Message( + sourcesCycle, + String.format("Encountered circular dependency spanning several threads. %s", + proxyCreationError.getMessage()), + null); } @Override diff --git a/core/src/com/google/inject/internal/State.java b/core/src/com/google/inject/internal/State.java index 8a828f2e..32c2da61 100644 --- a/core/src/com/google/inject/internal/State.java +++ b/core/src/com/google/inject/internal/State.java @@ -203,12 +203,6 @@ interface State { Object lock(); /** - * Returns the shared lock for all injector's singletons. This is a low-granularity lock - * to guarantee singleton creation semantics. - */ - Object singletonCreationLock(); - - /** * Returns all the scope bindings at this level and parent levels. */ Map<Class<? extends Annotation>, Scope> getScopes(); diff --git a/core/test/com/google/inject/ScopesTest.java b/core/test/com/google/inject/ScopesTest.java index 59aa596d..15c2d2ec 100644 --- a/core/test/com/google/inject/ScopesTest.java +++ b/core/test/com/google/inject/ScopesTest.java @@ -23,6 +23,7 @@ import static com.google.inject.name.Names.named; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.inject.name.Named; import com.google.inject.spi.Element; @@ -38,15 +39,18 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.Callable; import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; /** * @author crazybob@google.com (Bob Lee) @@ -409,16 +413,6 @@ public class ScopesTest extends TestCase { assertNull(injector.getInstance(String.class)); } - public void testSingletonScopeCreationOutsideOfScopingThrows() { - try { - Scopes.SINGLETON.scope(Key.get(String.class), null); - fail(); - } catch (OutOfScopeException expected) { - Asserts.assertContains(expected.getMessage(), - "Singleton scope should only be used from Injector"); - } - } - class RememberProviderScope implements Scope { final Map<Key<?>, Provider<?>> providers = Maps.newHashMap(); public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) { @@ -720,7 +714,8 @@ public class ScopesTest extends TestCase { bind(c).toProvider(Providers.of("c")).in(Scopes.NO_SCOPE); bind(d).toProvider(Providers.of("d")).in(Singleton.class); install(new PrivateModule() { - @Override protected void configure() { + @Override + protected void configure() { bind(f).toProvider(Providers.of("f")).in(Singleton.class); expose(f); } @@ -805,21 +800,47 @@ public class ScopesTest extends TestCase { assertEquals(2, ThrowingSingleton.nextInstanceId); } - static interface F {} + /** + * Should only be created by {@link SBarrierProvider}. + * + * <p>{@code S} stands for synchronization. + * + * @see SBarrierProvider + */ + static class S { - static class FImpl implements F {} + private S(int preventInjectionWithoutProvider) { + } + } - static class FProvider implements Provider<F> { + /** + * Provides all the instances of S simultaneously using {@link CyclicBarrier} with {@code + * nThreads}. Intended to be used for threads synchronization during injection. + */ + static class SBarrierProvider implements Provider<S> { - final CyclicBarrier bothThreadsAreInProvider = new CyclicBarrier(2); + final CyclicBarrier barrier; + volatile boolean barrierPassed = false; - public F get() { + SBarrierProvider(int nThreads) { + barrier = new CyclicBarrier(nThreads, new Runnable() { + public void run() { + // would finish before returning from await() for any thread + barrierPassed = true; + } + }); + } + + public S get() { try { - bothThreadsAreInProvider.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + if (!barrierPassed) { + // only if we're triggering barrier for the first time + barrier.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } } catch (Exception e) { throw new RuntimeException(e); } - return new FImpl(); + return new S(0); } } @@ -827,107 +848,345 @@ public class ScopesTest extends TestCase { * Tests that different injectors should not affect each other. * * <p>This creates a second thread to work in parallel, to create two instances of - * {@link F} as the same time. If the lock if not granular enough (i.e. JVM-wide) + * {@link S} as the same time. If the lock if not granular enough (i.e. JVM-wide) * then they would block each other creating a deadlock and await timeout. */ public void testInjectorsDontDeadlockOnSingletons() throws Exception { - final FProvider provider = new FProvider(); + final Provider<S> provider = new SBarrierProvider(2); final Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { - bind(F.class).toProvider(provider).in(Scopes.SINGLETON); + Thread.currentThread().setName("S.class[1]"); + bind(S.class).toProvider(provider).in(Scopes.SINGLETON); } }); final Injector secondInjector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { - bind(F.class).toProvider(provider).in(Scopes.SINGLETON); + Thread.currentThread().setName("S.class[2]"); + bind(S.class).toProvider(provider).in(Scopes.SINGLETON); } }); - Future<?> secondThreadResult = Executors.newSingleThreadExecutor().submit(new Runnable() { - public void run() { - secondInjector.getInstance(F.class); + Future<S> secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<S>() { + public S call() { + return secondInjector.getInstance(S.class); } }); - injector.getInstance(F.class); + S firstS = injector.getInstance(S.class); + S secondS = secondThreadResult.get(); - // verify no thrown exceptions - secondThreadResult.get(); + assertNotSame(firstS, secondS); } - static interface G {} + @ImplementedBy(GImpl.class) + interface G { - static interface H {} + } - static class GHImpl implements G, H {} + @Singleton + static class GImpl implements G { - static class GHProvider { + final H h; - final CyclicBarrier bothThreadsAreInProvider = new CyclicBarrier(2); + /** + * Relies on Guice implementation to inject S first and H later, which provides a barrier . + */ + @Inject + GImpl(S synchronizationBarrier, H h) { + this.h = h; + } + } - final Provider<G> gProvider = new Provider<G>() { - public G get() { - awaitBothThreadsInProvider(); - return new GHImpl(); - } - }; + @ImplementedBy(HImpl.class) + interface H { - final Provider<H> hProvider = new Provider<H>() { - public H get() { - awaitBothThreadsInProvider(); - return new GHImpl(); - } - }; + } - void awaitBothThreadsInProvider() { - try { - bothThreadsAreInProvider.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); - fail(); - } catch (TimeoutException expected) { - // first thread to arrive should timeout as we never enter providers from both threads - } catch (BrokenBarrierException expected) { - // second thread to arrive should fail as barrier is broken by the first thread's timeout - } catch (Exception e) { - throw new RuntimeException(e); - } + @Singleton + static class HImpl implements H { + + final G g; + + /** + * Relies on Guice implementation to inject S first and G later, which provides a barrier . + */ + @Inject + HImpl(S synchronizationBarrier, G g) throws Exception { + this.g = g; } } /** - * Tests that injectors int the same hierarchy do not create singletons in parallel. + * Tests that injector can create two singletons with circular dependency in parallel. * - * <p>This creates a second thread to work in parallel, to create instance of - * {@link G} and {@link H} as the same time. Both instances are created by injectors belonging - * to the same hierarchy. If the lock is too narrow (i.e. per Injector or per binding) - * instances would be created in parallel and test fail. + * <p>This creates two threads to work in parallel, to create instances of + * {@link G} and {@link H}. Creation is synchronized by injection of {@link S}, + * first thread would block until second would be inside a singleton creation as well. + * + * <p>Both instances are created by sibling injectors, that share singleton scope. + * Verifies that exactly one circular proxy object is created. */ - public void testSiblingInjectorsGettingDifferentSingletonsDontDeadlock() throws Exception { - final GHProvider ghProvider = new GHProvider(); - final Injector parentInjector = Guice.createInjector(); + public void testSiblingInjectorGettingCircularSingletonsOneCircularProxy() throws Exception { + final Provider<S> provider = new SBarrierProvider(2); + final Injector injector = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(S.class).toProvider(provider); + } + }); - Future<?> secondThreadResult = Executors.newSingleThreadExecutor().submit(new Runnable() { - public void run() { - parentInjector.createChildInjector(new AbstractModule() { - @Override - protected void configure() { - bind(H.class).toProvider(ghProvider.hProvider).in(Scopes.SINGLETON); - } - }).getInstance(H.class); + Future<G> firstThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<G>() { + public G call() { + Thread.currentThread().setName("G.class"); + return injector.createChildInjector().getInstance(G.class); + } + }); + Future<H> secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<H>() { + public H call() { + Thread.currentThread().setName("H.class"); + return injector.createChildInjector().getInstance(H.class); } }); - parentInjector.createChildInjector(new AbstractModule() { + // using separate threads to avoid potential deadlock on the main thread + // waiting twice as much to be sure that both would time out in their respective barriers + GImpl g = (GImpl) firstThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); + HImpl h = (HImpl) secondThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); + + // Check that G and H created are not proxied + assertTrue(!Scopes.isCircularProxy(g) && !Scopes.isCircularProxy(h)); + + // Check that we have no more than one circular proxy created + assertFalse(Scopes.isCircularProxy(g.h) && Scopes.isCircularProxy(h.g)); + + // Check that non-proxy variable points to another singleton + assertTrue(g.h == h || h.g == g); + + // Check correct proxy initialization as default equals implementation would + assertEquals(g.h, h); + assertEquals(h.g, g); + } + + @Singleton + static class I0 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + I0(I1 i) { + } + } + + @Singleton + static class I1 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + I1(S synchronizationBarrier, I2 i) { + } + } + + @Singleton + static class I2 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + I2(J1 j) { + } + } + + @Singleton + static class J0 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + J0(J1 j) { + } + } + + @Singleton + static class J1 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + J1(S synchronizationBarrier, J2 j) { + } + } + + @Singleton + static class J2 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + J2(K1 k) { + } + } + + @Singleton + static class K0 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + K0(K1 k) { + } + } + + @Singleton + static class K1 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + K1(S synchronizationBarrier, K2 k) { + } + } + + @Singleton + static class K2 { + + /** + * Relies on Guice implementation to inject S first, which provides a barrier . + */ + @Inject + K2(I1 i) { + } + } + + /** + * Check that circular dependencies on non-interfaces are correctly resolved in multi-threaded + * case. And that an error message constructed is a good one. + * + * <p>I0 -> I1 -> I2 -> J1 and J0 -> J1 -> J2 -> K1 and K0 -> K1 -> K2, + * where I1, J1 and K1 are created in parallel. + * + * <p>Creation is synchronized by injection of {@link S}, first thread would block until second + * would be inside a singleton creation as well. + * + * <p>Verifies that provision results in an error, that spans two threads and + * has a dependency cycle. + */ + + public void testUnresolvableSingletonCircularDependencyErrorMessage() throws Exception { + final Provider<S> provider = new SBarrierProvider(3); + final Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { - bind(G.class).toProvider(ghProvider.gProvider).in(Scopes.SINGLETON); + bind(S.class).toProvider(provider); + } + }); + + Future<I0> firstThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<I0>() { + public I0 call() { + Thread.currentThread().setName("I0.class"); + return injector.getInstance(I0.class); + } + }); + Future<J0> secondThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<J0>() { + public J0 call() { + Thread.currentThread().setName("J0.class"); + return injector.getInstance(J0.class); + } + }); + Future<K0> thirdThreadResult = Executors.newSingleThreadExecutor().submit(new Callable<K0>() { + public K0 call() { + Thread.currentThread().setName("K0.class"); + return injector.getInstance(K0.class); } - }).getInstance(G.class); + }); + + // using separate threads to avoid potential deadlock on the main thread + // waiting twice as much to be sure that both would time out in their respective barriers + Throwable firstException = null; + Throwable secondException = null; + Throwable thirdException = null; + try { + firstThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); + fail(); + } catch (ExecutionException e) { + firstException = e.getCause(); + } + try { + secondThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); + fail(); + } catch (ExecutionException e) { + secondException = e.getCause(); + } + try { + thirdThreadResult.get(DEADLOCK_TIMEOUT_SECONDS * 3, TimeUnit.SECONDS); + fail(); + } catch (ExecutionException e) { + thirdException = e.getCause(); + } - // verify no thrown exceptions - secondThreadResult.get(); + // verification of error messages generated + assertEquals(firstException.getClass(), ProvisionException.class); + assertEquals(secondException.getClass(), ProvisionException.class); + assertEquals(thirdException.getClass(), ProvisionException.class); + List<String> errorMessages = Lists.newArrayList( + String.format("%s\n%s\n%s", + firstException.getMessage(), secondException.getMessage(), thirdException.getMessage()) + .split("\\n\\n")); + Collections.sort(errorMessages, new Comparator<String>() { + @Override + public int compare(String s1, String s2) { + return s2.length() - s1.length(); + } + }); + // this is brittle, but turns out that second to longest message spans all threads + String errorMessage = errorMessages.get(1); + assertContains(errorMessage, + "Encountered circular dependency spanning several threads. Tried proxying " + + this.getClass().getName()); + assertFalse("Both I0 and J0 can not be a part of a dependency cycle", + errorMessage.contains(I0.class.getName()) && errorMessage.contains(J0.class.getName())); + assertFalse("Both J0 and K0 can not be a part of a dependency cycle", + errorMessage.contains(J0.class.getName()) && errorMessage.contains(K0.class.getName())); + assertFalse("Both K0 and I0 can not be a part of a dependency cycle", + errorMessage.contains(K0.class.getName()) && errorMessage.contains(I0.class.getName())); + + List<String> firstErrorLineForThread = new ArrayList<String>(); + boolean addNextLine = false; + for (String errorLine : errorMessage.split("\\n")) { + if (errorLine.contains("in thread")) { + addNextLine = true; + firstErrorLineForThread.add(errorLine); + } else if (addNextLine) { + addNextLine = false; + firstErrorLineForThread.add(errorLine); + } + } + assertEquals("we expect to see [T1, $A, T2, $B, T3, $C, T1, $A]", + 8, firstErrorLineForThread.size()); + assertEquals("first four elements should be different", + 6, new HashSet<String>(firstErrorLineForThread.subList(0, 6)).size()); + assertEquals(firstErrorLineForThread.get(6), firstErrorLineForThread.get(0)); + assertEquals(firstErrorLineForThread.get(7), firstErrorLineForThread.get(1)); + assertFalse("K0 thread could not be blocked by J0", + firstErrorLineForThread.get(0).contains("J0") + && firstErrorLineForThread.get(2).contains("K0")); + assertFalse("J0 thread could not be blocked by I0", + firstErrorLineForThread.get(0).contains("I0") + && firstErrorLineForThread.get(2).contains("J0")); + assertFalse("I0 thread could not be blocked by K0", + firstErrorLineForThread.get(0).contains("K0") + && firstErrorLineForThread.get(2).contains("I0")); } } diff --git a/core/test/com/google/inject/internal/CycleDetectingLockTest.java b/core/test/com/google/inject/internal/CycleDetectingLockTest.java new file mode 100644 index 00000000..0d10824f --- /dev/null +++ b/core/test/com/google/inject/internal/CycleDetectingLockTest.java @@ -0,0 +1,101 @@ +package com.google.inject.internal; + +import com.google.inject.internal.CycleDetectingLock.CycleDetectingLockFactory; + +import junit.framework.TestCase; + +import java.util.concurrent.Callable; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +public class CycleDetectingLockTest extends TestCase { + + static final long DEADLOCK_TIMEOUT_SECONDS = 1; + + /** + * Verifies that graph of threads' dependencies is not static and is calculated in runtime using + * information about specific locks. + * + * <pre> + * T1: Waits on S1 + * T2: Locks B, sends S1, waits on S2 + * T1: Locks A, start locking B, sends S2, waits on S3 + * T2: Unlocks B, start locking A, sends S3, finishes locking A, unlocks A + * T1: Finishes locking B, unlocks B, unlocks A + * </pre> + * + * <p>This should succeed, even though T1 was locked on T2 and T2 is locked on T1 when T2 locks + * A. Incorrect implementation detects a cycle waiting on S3. + */ + + public void testSingletonThreadsRuntimeCircularDependency() throws Exception { + final CyclicBarrier signal1 = new CyclicBarrier(2); + final CyclicBarrier signal2 = new CyclicBarrier(2); + final CyclicBarrier signal3 = new CyclicBarrier(2); + CycleDetectingLockFactory<String> lockFactory = new CycleDetectingLockFactory<String>(); + final CycleDetectingLock<String> lockA = + lockFactory.new ReentrantCycleDetectingLock("A", new ReentrantLock() { + @Override + public void lock() { + if (Thread.currentThread().getName().equals("T2")) { + try { + signal3.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + assertEquals("T1", Thread.currentThread().getName()); + } + super.lock(); + } + }); + final CycleDetectingLock<String> lockB = + lockFactory.new ReentrantCycleDetectingLock("B", new ReentrantLock() { + @Override + public void lock() { + if (Thread.currentThread().getName().equals("T1")) { + try { + signal2.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + signal3.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + assertEquals("T2", Thread.currentThread().getName()); + } + super.lock(); + } + }); + Future<Void> firstThreadResult = Executors.newSingleThreadExecutor().submit( + new Callable<Void>() { + public Void call() throws Exception { + Thread.currentThread().setName("T1"); + signal1.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue(lockA.lockOrDetectPotentialLocksCycle().isEmpty()); + assertTrue(lockB.lockOrDetectPotentialLocksCycle().isEmpty()); + lockB.unlock(); + lockA.unlock(); + return null; + } + }); + Future<Void> secondThreadResult = Executors.newSingleThreadExecutor().submit( + new Callable<Void>() { + public Void call() throws Exception { + Thread.currentThread().setName("T2"); + assertTrue(lockB.lockOrDetectPotentialLocksCycle().isEmpty()); + signal1.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + signal2.await(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS); + lockB.unlock(); + assertTrue(lockA.lockOrDetectPotentialLocksCycle().isEmpty()); + lockA.unlock(); + return null; + } + }); + + firstThreadResult.get(); + secondThreadResult.get(); + } +} diff --git a/extensions/dagger-adapter/lib/dagger-2.0-20150205.014011-14.jar b/extensions/dagger-adapter/lib/dagger-2.0-20150205.014011-14.jar Binary files differdeleted file mode 100644 index 3690cdc7..00000000 --- a/extensions/dagger-adapter/lib/dagger-2.0-20150205.014011-14.jar +++ /dev/null diff --git a/extensions/dagger-adapter/lib/dagger-2.0.jar b/extensions/dagger-adapter/lib/dagger-2.0.jar Binary files differnew file mode 100644 index 00000000..a7257df4 --- /dev/null +++ b/extensions/dagger-adapter/lib/dagger-2.0.jar diff --git a/extensions/dagger-adapter/pom.xml b/extensions/dagger-adapter/pom.xml index 10af044f..5bfc1f3e 100644 --- a/extensions/dagger-adapter/pom.xml +++ b/extensions/dagger-adapter/pom.xml @@ -18,7 +18,7 @@ <dependency> <groupId>com.google.dagger</groupId> <artifactId>dagger</artifactId> - <version>2.0-SNAPSHOT</version> + <version>2.0</version> </dependency> </dependencies> </project> diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MapKey.java b/extensions/multibindings/src/com/google/inject/multibindings/MapKey.java index bcb6a3b8..2fc3374d 100644 --- a/extensions/multibindings/src/com/google/inject/multibindings/MapKey.java +++ b/extensions/multibindings/src/com/google/inject/multibindings/MapKey.java @@ -54,5 +54,5 @@ public @interface MapKey { * instances will be the keys. If {@code unwrapValue} is true, the value() type of key type * annotation will be the key type for injected map and the value instances will be the keys. */ - boolean unwrapValue(); + boolean unwrapValue() default true; } |