Skip to content

Commit 5e6c933

Browse files
timofeybsameb
authored andcommitted
Implement more granular locks for a Singleton scope in Guice.
Now when you can create two independent singletons using the same injector in different threads. This make it easy to create scopes creating singletons using thread pools with all the concurrency being done by Guice. As a nice side effect Singleton scope is no longer treated specially in Guice codebase. The obvious problem to solve is potential deadlocks: A requires B, B requires C, C requires A where all are singletons and all are created simultaneously. It's impossible to detect this deadlock using information within one thread, so we have to have a shared storage. An idea is to have a map of creators' locks and a map of which threads are waiting for other singletons to be created. Using this information circular dependencies are trivially discovered within O(N) where N is a number of concurrent threads. Important to not that no other deadlock scenarios within Guice code is introduced as Guice does not expose any other scopes that can span several threads. Now it would be possible for client code to deadlock on itself with two lazy singletons calling each other's providers during creation. This is deemed as a non-issue as it is up to the client to write a thread-safe code. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=91610630
1 parent 6c85843 commit 5e6c933

19 files changed

Lines changed: 1139 additions & 207 deletions

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public Boolean visit(ProviderInstanceBinding<? extends T> binding) {
111111
// always visited with Binding<T>
112112
@SuppressWarnings("unchecked")
113113
InternalFactory<T> factory = new InternalFactoryToInitializableAdapter<T>(
114-
initializable, source, !injector.options.disableCircularProxies,
114+
initializable, source,
115115
injector.provisionListenerStore.get((ProviderInstanceBinding<T>)binding));
116116
InternalFactory<? extends T> scopedFactory
117117
= Scoping.scope(key, injector, factory, source, scoping);
@@ -127,7 +127,7 @@ public Boolean visit(ProviderKeyBinding<? extends T> binding) {
127127
// always visited with Binding<T>
128128
@SuppressWarnings("unchecked")
129129
BoundProviderFactory<T> boundProviderFactory = new BoundProviderFactory<T>(
130-
injector, providerKey, source, !injector.options.disableCircularProxies,
130+
injector, providerKey, source,
131131
injector.provisionListenerStore.get((ProviderKeyBinding<T>) binding));
132132
bindingData.addCreationListener(boundProviderFactory);
133133
InternalFactory<? extends T> scopedFactory = Scoping.scope(

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@ final class BoundProviderFactory<T> extends ProviderInternalFactory<T> implement
3838
InjectorImpl injector,
3939
Key<? extends javax.inject.Provider<? extends T>> providerKey,
4040
Object source,
41-
boolean allowProxy,
4241
ProvisionListenerStackCallback<T> provisionCallback) {
43-
super(source, allowProxy);
42+
super(source);
4443
this.provisionCallback = checkNotNull(provisionCallback, "provisionCallback");
4544
this.injector = injector;
4645
this.providerKey = providerKey;
@@ -60,7 +59,7 @@ public T get(Errors errors, InternalContext context, Dependency<?> dependency, b
6059
try {
6160
errors = errors.withSource(providerKey);
6261
javax.inject.Provider<? extends T> provider = providerFactory.get(errors, context, dependency, true);
63-
return circularGet(provider, errors, context, dependency, linked, provisionCallback);
62+
return circularGet(provider, errors, context, dependency, provisionCallback);
6463
} finally {
6564
context.popState();
6665
}

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.google.inject.internal;
1818

19+
import com.google.inject.internal.InjectorImpl.InjectorOptions;
20+
1921
import java.lang.reflect.Proxy;
2022
import java.util.ArrayList;
2123
import java.util.List;
@@ -57,11 +59,11 @@ public void finishConstruction() {
5759
invocationHandlers = null;
5860
}
5961

60-
public Object createProxy(Errors errors, Class<?> expectedType) throws ErrorsException {
61-
// TODO: if I create a proxy which implements all the interfaces of
62-
// the implementation type, I'll be able to get away with one proxy
63-
// instance (as opposed to one per caller).
64-
62+
public Object createProxy(Errors errors, InjectorOptions injectorOptions,
63+
Class<?> expectedType) throws ErrorsException {
64+
if (injectorOptions.disableCircularProxies) {
65+
throw errors.circularProxiesDisabled(expectedType).toException();
66+
}
6567
if (!expectedType.isInterface()) {
6668
throw errors.cannotSatisfyCircularDependency(expectedType).toException();
6769
}
@@ -73,6 +75,9 @@ public Object createProxy(Errors errors, Class<?> expectedType) throws ErrorsExc
7375
DelegatingInvocationHandler<T> invocationHandler = new DelegatingInvocationHandler<T>();
7476
invocationHandlers.add(invocationHandler);
7577

78+
// TODO: if I create a proxy which implements all the interfaces of
79+
// the implementation type, I'll be able to get away with one proxy
80+
// instance (as opposed to one per caller).
7681
ClassLoader classLoader = BytecodeGen.getClassLoader(expectedType);
7782
return expectedType.cast(Proxy.newProxyInstance(classLoader,
7883
new Class[] { expectedType, CircularDependencyProxy.class }, invocationHandler));
@@ -83,6 +88,8 @@ public void setProxyDelegates(T delegate) {
8388
for (DelegatingInvocationHandler<T> handler : invocationHandlers) {
8489
handler.setDelegate(delegate);
8590
}
91+
// initialization of each handler can happen no more than once
92+
invocationHandlers = null;
8693
}
8794
}
8895
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ private static boolean hasAtInject(Constructor cxtor) {
133133

134134
@SuppressWarnings("unchecked") // the result type always agrees with the ConstructorInjector type
135135
public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
136-
factory.allowCircularProxy = !injector.options.disableCircularProxies;
137136
factory.constructorInjector =
138137
(ConstructorInjector<T>) injector.constructors.get(constructorInjectionPoint, errors);
139138
factory.provisionCallback =
@@ -246,7 +245,6 @@ public int hashCode() {
246245
private static class Factory<T> implements InternalFactory<T> {
247246
private final boolean failIfNotLinked;
248247
private final Key<?> key;
249-
private boolean allowCircularProxy;
250248
private ConstructorInjector<T> constructorInjector;
251249
private ProvisionListenerStackCallback<T> provisionCallback;
252250

@@ -267,7 +265,7 @@ public T get(Errors errors, InternalContext context, Dependency<?> dependency, b
267265
// This may not actually be safe because it could return a super type of T (if that's all the
268266
// client needs), but it should be OK in practice thanks to the wonders of erasure.
269267
return (T) constructorInjector.construct(errors, context,
270-
dependency.getKey().getTypeLiteral().getRawType(), allowCircularProxy, provisionCallback);
268+
dependency.getKey().getTypeLiteral().getRawType(), provisionCallback);
271269
}
272270
}
273271
}

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,16 @@ ConstructionProxy<T> getConstructionProxy() {
5959
* it may return a proxy.
6060
*/
6161
Object construct(final Errors errors, final InternalContext context,
62-
Class<?> expectedType, boolean allowProxy,
62+
Class<?> expectedType,
6363
ProvisionListenerStackCallback<T> provisionCallback)
6464
throws ErrorsException {
6565
final ConstructionContext<T> constructionContext = context.getConstructionContext(this);
6666

6767
// We have a circular reference between constructors. Return a proxy.
6868
if (constructionContext.isConstructing()) {
69-
if(!allowProxy) {
70-
throw errors.circularProxiesDisabled(expectedType).toException();
71-
} else {
72-
// TODO (crazybob): if we can't proxy this object, can we proxy the other object?
73-
return constructionContext.createProxy(errors, expectedType);
74-
}
69+
// TODO (crazybob): if we can't proxy this object, can we proxy the other object?
70+
return constructionContext.createProxy(
71+
errors, context.getInjectorOptions(), expectedType);
7572
}
7673

7774
// If we're re-entering this factory while injecting fields or methods,
@@ -85,7 +82,7 @@ Object construct(final Errors errors, final InternalContext context,
8582
try {
8683
// Optimization: Don't go through the callback stack if we have no listeners.
8784
if (!provisionCallback.hasListeners()) {
88-
return provision(errors, context, constructionContext);
85+
return provision(errors, context, constructionContext);
8986
} else {
9087
return provisionCallback.provision(errors, context, new ProvisionCallback<T>() {
9188
public T call() throws ErrorsException {

0 commit comments

Comments
 (0)