Skip to content

Commit d7aa953

Browse files
timofeybsameb
authored andcommitted
Making Singleton's creation lock less coarse.
Singleton is defined as a scope which creates no more than one object per injector. It's highly confusing when you catch a deadlock between two unrelated injectors due to the same class injection. Problem is demonstrated using a test that recreates scenario when one thread injecting class can block other thread to use its own injector. Proposed solution is to use Injector-wide locks in a singleton. ThreadLocal as a way to store current state. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=78469951
1 parent 03c2bbf commit d7aa953

File tree

7 files changed

+283
-77
lines changed

7 files changed

+283
-77
lines changed

core/src/com/google/inject/Scopes.java

Lines changed: 2 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
package com.google.inject;
1818

1919
import com.google.inject.internal.CircularDependencyProxy;
20-
import com.google.inject.internal.InternalInjectorCreator;
2120
import com.google.inject.internal.LinkedBindingImpl;
21+
import com.google.inject.internal.SingletonScope;
2222
import com.google.inject.spi.BindingScopingVisitor;
2323
import com.google.inject.spi.ExposedBinding;
2424

@@ -33,71 +33,10 @@ public class Scopes {
3333

3434
private Scopes() {}
3535

36-
/** A sentinel value representing null. */
37-
private static final Object NULL = new Object();
38-
3936
/**
4037
* One instance per {@link Injector}. Also see {@code @}{@link Singleton}.
4138
*/
42-
public static final Scope SINGLETON = new Scope() {
43-
public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
44-
return new Provider<T>() {
45-
/*
46-
* The lazily initialized singleton instance. Once set, this will either have type T or will
47-
* be equal to NULL.
48-
*/
49-
private volatile Object instance;
50-
51-
// DCL on a volatile is safe as of Java 5, which we obviously require.
52-
@SuppressWarnings("DoubleCheckedLocking")
53-
public T get() {
54-
if (instance == null) {
55-
/*
56-
* Use a pretty coarse lock. We don't want to run into deadlocks
57-
* when two threads try to load circularly-dependent objects.
58-
* Maybe one of these days we will identify independent graphs of
59-
* objects and offer to load them in parallel.
60-
*
61-
* This block is re-entrant for circular dependencies.
62-
*/
63-
synchronized (InternalInjectorCreator.class) {
64-
if (instance == null) {
65-
T provided = creator.get();
66-
67-
// don't remember proxies; these exist only to serve circular dependencies
68-
if (isCircularProxy(provided)) {
69-
return provided;
70-
}
71-
72-
Object providedOrSentinel = (provided == null) ? NULL : provided;
73-
if (instance != null && instance != providedOrSentinel) {
74-
throw new ProvisionException(
75-
"Provider was reentrant while creating a singleton");
76-
}
77-
78-
instance = providedOrSentinel;
79-
}
80-
}
81-
}
82-
83-
Object localInstance = instance;
84-
// This is safe because instance has type T or is equal to NULL
85-
@SuppressWarnings("unchecked")
86-
T returnedInstance = (localInstance != NULL) ? (T) localInstance : null;
87-
return returnedInstance;
88-
}
89-
90-
@Override
91-
public String toString() {
92-
return String.format("%s[%s]", creator, SINGLETON);
93-
}
94-
};
95-
}
96-
97-
@Override public String toString() {
98-
return "Scopes.SINGLETON";
99-
}
100-
};
39+
public static final Scope SINGLETON = new SingletonScope();
10140

10241
/**
10342
* No scope; the same as not applying any scope at all. Each time the

core/src/com/google/inject/internal/InheritingState.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,13 @@ final class InheritingState implements State {
5858
private final List<ProvisionListenerBinding> provisionListenerBindings = Lists.newArrayList();
5959
private final WeakKeySet blacklistedKeys;
6060
private final Object lock;
61+
private final Object singletonCreationLock;
6162

6263
InheritingState(State parent) {
6364
this.parent = checkNotNull(parent, "parent");
6465
this.lock = (parent == State.NONE) ? this : parent.lock();
66+
this.singletonCreationLock =
67+
(parent == State.NONE) ? new Object() : parent.singletonCreationLock();
6568
this.blacklistedKeys = new WeakKeySet(lock);
6669
}
6770

@@ -172,6 +175,10 @@ public Object lock() {
172175
return lock;
173176
}
174177

178+
public Object singletonCreationLock() {
179+
return singletonCreationLock;
180+
}
181+
175182
public Map<Class<? extends Annotation>, Scope> getScopes() {
176183
ImmutableMap.Builder<Class<? extends Annotation>, Scope> builder = ImmutableMap.builder();
177184
for (Map.Entry<Class<? extends Annotation>, ScopeBinding> entry : scopes.entrySet()) {

core/src/com/google/inject/internal/Scoping.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,14 @@ static <T> InternalFactory<? extends T> scope(Key<T> key, InjectorImpl injector,
239239

240240
Scope scope = scoping.getScopeInstance();
241241

242-
Provider<T> scoped
243-
= scope.scope(key, new ProviderToInternalFactoryAdapter<T>(injector, creator));
244-
return new InternalFactoryToProviderAdapter<T>(scoped, source);
242+
try {
243+
SingletonScope.singletonCreationPerRootInjectorLock.set(injector.state.singletonCreationLock());
244+
Provider<T> scoped
245+
= scope.scope(key, new ProviderToInternalFactoryAdapter<T>(injector, creator));
246+
return new InternalFactoryToProviderAdapter<T>(scoped, source);
247+
} finally {
248+
SingletonScope.singletonCreationPerRootInjectorLock.set(null);
249+
}
245250
}
246251

247252
/**
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.google.inject.internal;
2+
3+
import com.google.inject.Injector;
4+
import com.google.inject.Key;
5+
import com.google.inject.OutOfScopeException;
6+
import com.google.inject.Provider;
7+
import com.google.inject.ProvisionException;
8+
import com.google.inject.Scope;
9+
import com.google.inject.Scopes;
10+
import com.google.inject.Singleton;
11+
12+
/**
13+
* One instance per {@link Injector}. Also see {@code @}{@link Singleton}.
14+
*/
15+
public class SingletonScope implements Scope {
16+
17+
/** A sentinel value representing null. */
18+
private static final Object NULL = new Object();
19+
20+
/**
21+
* Lock to use for new instances creation. This allows a per-root-Injector singleton lock,
22+
* instead of a global lock across the JVM. Is set only during call to {@link #scope}.
23+
*
24+
* This is necessary because users have coded to a single {@link Scopes#SINGLETON} instance,
25+
* and we cannot change that. Additionally, we can't reference the injector from a Key or
26+
* Provider (the only variables available to the {@link #scope} method). Therefore, we rely
27+
* on the injector implementation to explicitly set/unset the lock surrounding
28+
* creation of the Provider the scope creates.
29+
*
30+
* @see {@link Scoping#scope(Key, InjectorImpl, InternalFactory, Object, Scoping)} for details.
31+
*/
32+
static final ThreadLocal<Object> singletonCreationPerRootInjectorLock =
33+
new ThreadLocal<Object>();
34+
35+
public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
36+
// lock is referenced from anonymous class instance
37+
final Object rootInjectorLock = singletonCreationPerRootInjectorLock.get();
38+
if (rootInjectorLock == null) {
39+
throw new OutOfScopeException("Singleton scope should only be used from Injector");
40+
}
41+
return new Provider<T>() {
42+
/*
43+
* The lazily initialized singleton instance. Once set, this will either have type T or will
44+
* be equal to NULL.
45+
*/
46+
private volatile Object instance;
47+
48+
// DCL on a volatile is safe as of Java 5, which we obviously require.
49+
@SuppressWarnings("DoubleCheckedLocking")
50+
public T get() {
51+
if (instance == null) {
52+
/*
53+
* Use a pretty coarse lock. We don't want to run into deadlocks
54+
* when two threads try to load circularly-dependent objects.
55+
* Maybe one of these days we will identify independent graphs of
56+
* objects and offer to load them in parallel.
57+
*
58+
* This block is re-entrant for circular dependencies.
59+
*/
60+
synchronized (rootInjectorLock) {
61+
if (instance == null) {
62+
T provided = creator.get();
63+
64+
// don't remember proxies; these exist only to serve circular dependencies
65+
if (Scopes.isCircularProxy(provided)) {
66+
return provided;
67+
}
68+
69+
Object providedOrSentinel = (provided == null) ? NULL : provided;
70+
if (instance != null && instance != providedOrSentinel) {
71+
throw new ProvisionException(
72+
"Provider was reentrant while creating a singleton");
73+
}
74+
75+
instance = providedOrSentinel;
76+
}
77+
}
78+
}
79+
80+
Object localInstance = instance;
81+
// This is safe because instance has type T or is equal to NULL
82+
@SuppressWarnings("unchecked")
83+
T returnedInstance = (localInstance != NULL) ? (T) localInstance : null;
84+
return returnedInstance;
85+
}
86+
87+
@Override
88+
public String toString() {
89+
return String.format("%s[%s]", creator, Scopes.SINGLETON);
90+
}
91+
};
92+
}
93+
94+
@Override public String toString() {
95+
return "Scopes.SINGLETON";
96+
}
97+
}

core/src/com/google/inject/internal/State.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ public Object lock() {
120120
throw new UnsupportedOperationException();
121121
}
122122

123+
public Object singletonCreationLock() {
124+
throw new UnsupportedOperationException();
125+
}
126+
123127
public Map<Class<? extends Annotation>, Scope> getScopes() {
124128
return ImmutableMap.of();
125129
}
@@ -184,6 +188,12 @@ TypeConverterBinding getConverter(
184188
*/
185189
Object lock();
186190

191+
/**
192+
* Returns the shared lock for all injector's singletons. This is a low-granularity lock
193+
* to guarantee singleton creation semantics.
194+
*/
195+
Object singletonCreationLock();
196+
187197
/**
188198
* Returns all the scope bindings at this level and parent levels.
189199
*/

0 commit comments

Comments
 (0)