Hierarchical Parent Child Chained injectors are now threadsafe

git-svn-id: https://google-guice.googlecode.com/svn/trunk@662 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/BindingProcessor.java b/src/com/google/inject/BindingProcessor.java
index 8c6df23..42f9634 100644
--- a/src/com/google/inject/BindingProcessor.java
+++ b/src/com/google/inject/BindingProcessor.java
@@ -27,7 +27,6 @@
 import com.google.inject.spi.InjectionPoint;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
-import java.lang.reflect.Type;
 import java.util.List;
 import java.util.Set;
 
@@ -169,8 +168,6 @@
       }
 
       public Void visitUntargetted() {
-        final Type type = key.getTypeLiteral().getType();
-
         // Error: Missing implementation.
         // Example: bind(Date.class).annotatedWith(Red.class);
         // We can't assume abstract types aren't injectable. They may have an
@@ -259,8 +256,6 @@
       return;
     }
 
-    // TODO: make getExplicitBinding() and blacklist() atomic
-
     // prevent the parent from creating a JIT binding for this key
     state.parent().blacklist(key);
     state.putBinding(key, binding);
diff --git a/src/com/google/inject/InheritingState.java b/src/com/google/inject/InheritingState.java
index 0d08429..2faa186 100644
--- a/src/com/google/inject/InheritingState.java
+++ b/src/com/google/inject/InheritingState.java
@@ -31,8 +31,6 @@
  */
 class InheritingState implements State {
 
-  // TODO(jessewilson): think about what we need to do w.r.t. concurrency
-
   private final State parent;
   private final Map<Key<?>, Binding<?>> explicitBindingsMutable = Maps.newHashMap();
   private final Map<Key<?>, Binding<?>> explicitBindings
@@ -41,9 +39,11 @@
   private final List<MatcherAndConverter> converters = Lists.newArrayList();
   private final List<MethodAspect> methodAspects = Lists.newArrayList();
   private final WeakKeySet blacklistedKeys = new WeakKeySet();
+  private final Object lock;
 
   InheritingState(State parent) {
     this.parent = checkNotNull(parent, "parent");
+    this.lock = (parent == State.NONE) ? this : parent.lock();
   }
 
   public State parent() {
@@ -116,4 +116,8 @@
   public boolean isBlacklisted(Key<?> key) {
     return blacklistedKeys.contains(key);
   }
+
+  public Object lock() {
+    return lock;
+  }
 }
diff --git a/src/com/google/inject/InjectorBuilder.java b/src/com/google/inject/InjectorBuilder.java
index cf47351..8932423 100644
--- a/src/com/google/inject/InjectorBuilder.java
+++ b/src/com/google/inject/InjectorBuilder.java
@@ -36,7 +36,16 @@
 import java.util.logging.Logger;
 
 /**
- * Builds a dependency injection {@link Injector}.
+ * Builds a dependency injection {@link Injector}. Injector construction happens in two phases.
+ * <ol>
+ *   <li>Static building. In this phase, we interpret commands, create bindings, and inspect 
+ *     dependencies. During this phase, we hold a lock to ensure consistency with parent injectors.
+ *     No user code is executed in this phase.</li>
+ *   <li>Dynamic injection. In this phase, we call user code. We inject members that requested
+ *     injection. This may require user's objects be created and their providers be called. And we
+ *     create eager singletons. In this phase, user code may have started other threads. This phase
+ *     is not executed for injectors created using {@link Stage#TOOL the tool stage}</li>
+ * </ol>
  *
  * @author crazybob@google.com (Bob Lee)
  * @author jessewilson@google.com (Jesse Wilson)
@@ -85,69 +94,71 @@
 
     injector = new InjectorImpl(parent);
 
-    // bind Stage and Singleton if this is a top-level injector
-    if (parent == null) {
-      modules.add(0, new RootModule(stage));
-    }
+    buildStatically();
 
-    elements.addAll(Elements.getElements(stage, modules));
-
-    buildCoreInjector();
-
-    validate();
-
-    errors.throwCreationExceptionIfErrorsExist();
-
-    // If we're in the tool stage, stop here. Don't eagerly inject or load
-    // anything.
+    // If we're in the tool stage, stop here. Don't eagerly inject or load anything.
     if (stage == Stage.TOOL) {
       return new ToolStageInjector(injector);
     }
 
-    fulfillInjectionRequests();
-
-    if (!elements.isEmpty()) {
-      throw new AssertionError("Failed to execute " + elements);
-    }
+    injectDynamically();
 
     return injector;
   }
 
-  /** Builds the injector. */
-  private void buildCoreInjector() {
-    new MessageProcessor(errors)
-        .processCommands(elements);
+  /**
+   * Synchronize while we're building up the bindings and other injector state. This ensures that
+   * the JIT bindings in the parent injector don't change while we're being built
+   */
+  void buildStatically() {
+    synchronized (injector.state.lock()) {
+      // bind Stage and Singleton if this is a top-level injector
+      if (parent == null) {
+        modules.add(0, new RootModule(stage));
+      }
 
-    InterceptorBindingProcessor interceptorCommandProcessor
-        = new InterceptorBindingProcessor(errors, injector.state);
-    interceptorCommandProcessor.processCommands(elements);
-    injector.constructionProxyFactory = interceptorCommandProcessor.createProxyFactory();
-    stopwatch.resetAndLog("Interceptors creation");
+      elements.addAll(Elements.getElements(stage, modules));
+      stopwatch.resetAndLog("Module execution");
 
-    new ScopeBindingProcessor(errors, injector.state).processCommands(elements);
-    stopwatch.resetAndLog("Scopes creation");
+      new MessageProcessor(errors).processCommands(elements);
 
-    new TypeConverterBindingProcessor(errors, injector.state).processCommands(elements);
-    stopwatch.resetAndLog("Converters creation");
+      InterceptorBindingProcessor interceptorCommandProcessor
+          = new InterceptorBindingProcessor(errors, injector.state);
+      interceptorCommandProcessor.processCommands(elements);
+      injector.constructionProxyFactory = interceptorCommandProcessor.createProxyFactory();
+      stopwatch.resetAndLog("Interceptors creation");
 
-    bindInjector();
-    bindLogger();
-    bindCommandProcesor = new BindingProcessor(errors,
-        injector, injector.state, injector.initializer);
-    bindCommandProcesor.processCommands(elements);
-    bindCommandProcesor.createUntargettedBindings();
-    stopwatch.resetAndLog("Binding creation");
+      new ScopeBindingProcessor(errors, injector.state).processCommands(elements);
+      stopwatch.resetAndLog("Scopes creation");
 
-    injector.index();
-    stopwatch.resetAndLog("Binding indexing");
+      new TypeConverterBindingProcessor(errors, injector.state).processCommands(elements);
+      stopwatch.resetAndLog("Converters creation");
 
-    injectionCommandProcessor = new InjectionRequestProcessor(errors, injector.initializer);
-    injectionCommandProcessor.processCommands(elements);
-    stopwatch.resetAndLog("Static injection");
+      bindInjector();
+      bindLogger();
+      bindCommandProcesor = new BindingProcessor(errors,
+          injector, injector.state, injector.initializer);
+      bindCommandProcesor.processCommands(elements);
+      bindCommandProcesor.createUntargettedBindings();
+      stopwatch.resetAndLog("Binding creation");
+
+      injector.index();
+      stopwatch.resetAndLog("Binding indexing");
+
+      injectionCommandProcessor = new InjectionRequestProcessor(errors, injector.initializer);
+      injectionCommandProcessor.processCommands(elements);
+      stopwatch.resetAndLog("Static injection");
+
+      validateStatically();
+
+      if (!elements.isEmpty()) {
+        throw new AssertionError("Failed to execute " + elements);
+      }
+    }
   }
 
   /** Validate everything that we can validate now that the injector is ready for use. */
-  private void validate() {
+  private void validateStatically() {
     bindCommandProcesor.runCreationListeners(injector);
     stopwatch.resetAndLog("Validation");
 
@@ -163,8 +174,12 @@
     errors.throwCreationExceptionIfErrorsExist();
   }
 
-  /** Inject everything that can be injected. */
-  private void fulfillInjectionRequests() {
+  /**
+   * Inject everything that can be injected. This method is intentionally not synchronized. If we
+   * locked while injecting members (ie. running user code), things would deadlock should the user
+   * code build a just-in-time binding from another thread.
+   */
+  private void injectDynamically() {
     injectionCommandProcessor.injectMembers(injector);
     stopwatch.resetAndLog("Static member injection");
 
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index 54908af..a827c46 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -152,7 +152,7 @@
 
     // TODO: synch should span parent and child
 
-    synchronized (jitBindings) {
+    synchronized (state.lock()) {
       // try to get the JIT binding from the parent injector
       if (parent != null) {
         try {
@@ -321,7 +321,7 @@
   <T> void initializeBinding(BindingImpl<T> binding, Errors errors) throws ErrorsException {
     // Put the partially constructed binding in the map a little early. This enables us to handle
     // circular dependencies. Example: FooImpl -> BarImpl -> FooImpl.
-    // Note: We don't need to synchronize on jitBindings during injector creation.
+    // Note: We don't need to synchronize on state.lock() during injector creation.
     if (binding instanceof ClassBindingImpl<?>) {
       Key<T> key = binding.getKey();
       jitBindings.put(key, binding);
diff --git a/src/com/google/inject/State.java b/src/com/google/inject/State.java
index 580eeae..23ea08e 100644
--- a/src/com/google/inject/State.java
+++ b/src/com/google/inject/State.java
@@ -84,6 +84,10 @@
     public boolean isBlacklisted(Key<?> key) {
       return true;
     }
+
+    public Object lock() {
+      throw new UnsupportedOperationException();
+    }
   };
 
   State parent();
@@ -126,4 +130,10 @@
    * one of this injector's descendent's has bound the key.
    */
   boolean isBlacklisted(Key<?> key);
+
+  /**
+   * Returns the shared lock for all injector data. This is a low-granularity, high-contention lock
+   * to be used when reading mutable data (ie. just-in-time bindings, and binding blacklists).
+   */
+  Object lock();
 }
diff --git a/test/com/google/inject/InjectorTest.java b/test/com/google/inject/InjectorTest.java
index 4f339de..733e399 100644
--- a/test/com/google/inject/InjectorTest.java
+++ b/test/com/google/inject/InjectorTest.java
@@ -21,6 +21,12 @@
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicReference;
 import junit.framework.TestCase;
 
 /**
@@ -387,4 +393,29 @@
       return this;
     }
   }
+
+  public void testJitBindingFromAnotherThreadDuringInjection() {
+    final ExecutorService executorService = Executors.newSingleThreadExecutor();
+    final AtomicReference<JustInTime> got = new AtomicReference<JustInTime>();
+
+    Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        requestInjection(new Object() {
+          @Inject void initialize(final Injector injector)
+              throws ExecutionException, InterruptedException {
+            Future<JustInTime> future = executorService.submit(new Callable<JustInTime>() {
+              public JustInTime call() throws Exception {
+                return injector.getInstance(JustInTime.class);
+              }
+            });
+            got.set(future.get());
+          }
+        });
+      }
+    });
+
+    assertNotNull(got.get());
+  }
+
+  static class JustInTime {}
 }