• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.inject;
18 
19 import static com.google.inject.Asserts.getClassPathUrls;
20 
21 import java.net.URLClassLoader;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.concurrent.CountDownLatch;
25 import junit.framework.TestCase;
26 
27 /** @author jessewilson@google.com (Jesse Wilson) */
28 public class EagerSingletonTest extends TestCase {
29 
30   @Override
setUp()31   public void setUp() {
32     A.instanceCount = 0;
33     B.instanceCount = 0;
34     C.instanceCount = 0;
35   }
36 
testJustInTimeEagerSingletons()37   public void testJustInTimeEagerSingletons() {
38     Guice.createInjector(
39         Stage.PRODUCTION,
40         new AbstractModule() {
41           @Override
42           protected void configure() {
43             // create a just-in-time binding for A
44             getProvider(A.class);
45 
46             // create a just-in-time binding for C
47             requestInjection(
48                 new Object() {
49                   @Inject
50                   void inject(Injector injector) {
51                     injector.getInstance(C.class);
52                   }
53                 });
54           }
55         });
56 
57     assertEquals(1, A.instanceCount);
58     assertEquals(
59         "Singletons discovered when creating singletons should not be built eagerly",
60         0,
61         B.instanceCount);
62     assertEquals(1, C.instanceCount);
63   }
64 
testJustInTimeSingletonsAreNotEager()65   public void testJustInTimeSingletonsAreNotEager() {
66     Injector injector = Guice.createInjector(Stage.PRODUCTION);
67     injector.getProvider(B.class);
68     assertEquals(0, B.instanceCount);
69   }
70 
testChildEagerSingletons()71   public void testChildEagerSingletons() {
72     Injector parent = Guice.createInjector(Stage.PRODUCTION);
73     parent.createChildInjector(
74         new AbstractModule() {
75           @Override
76           protected void configure() {
77             bind(D.class).to(C.class);
78           }
79         });
80 
81     assertEquals(1, C.instanceCount);
82   }
83 
84   // there used to be a bug that caused a concurrent modification exception if jit bindings were
85   // loaded during eager singleton creation due to failur to apply the lock when iterating over
86   // all bindings.
87 
testJustInTimeEagerSingletons_multipleThreads()88   public void testJustInTimeEagerSingletons_multipleThreads() throws Exception {
89     // in order to make the data race more likely we need a lot of jit bindings.  The easiest thing
90     // is just to 'copy' out class for B a bunch of times.
91     final List<Class<?>> jitBindings = new ArrayList<>();
92     for (int i = 0; i < 1000; i++) {
93       jitBindings.add(copyClass(B.class));
94     }
95     Guice.createInjector(
96         Stage.PRODUCTION,
97         new AbstractModule() {
98           @Override
99           protected void configure() {
100             // create a just-in-time binding for A
101             getProvider(A.class);
102 
103             // create a just-in-time binding for C
104             requestInjection(
105                 new Object() {
106                   @Inject
107                   void inject(final Injector injector) throws Exception {
108                     final CountDownLatch latch = new CountDownLatch(1);
109                     new Thread() {
110                       @Override
111                       public void run() {
112                         latch.countDown();
113                         for (Class<?> jitBinding : jitBindings) {
114                           // this causes the binding to be created
115                           injector.getProvider(jitBinding);
116                         }
117                       }
118                     }.start();
119                     latch.await(); // make sure the thread is running before returning
120                   }
121                 });
122           }
123         });
124 
125     assertEquals(1, A.instanceCount);
126     // our thread runs in parallel with eager singleton loading so some there should be some number
127     // N such that 0<=N <jitBindings.size() and all classes in jitBindings with an index < N will
128     // have been initialized.  Assert that!
129     int prev = -1;
130     int index = 0;
131     for (Class<?> jitBinding : jitBindings) {
132       int instanceCount = (Integer) jitBinding.getDeclaredField("instanceCount").get(null);
133       if (instanceCount != 0 && instanceCount != 1) {
134         fail("Should only have created zero or one instances, got " + instanceCount);
135       }
136       if (prev == -1) {
137         prev = instanceCount;
138       } else if (prev != instanceCount) {
139         if (prev != 1 && instanceCount != 0) {
140           fail("initialized later JIT bindings before earlier ones at index " + index);
141         }
142         prev = instanceCount;
143       }
144       index++;
145     }
146   }
147 
148   /** Creates a copy of a class in a child classloader. */
copyClass(final Class<?> cls)149   private static Class<?> copyClass(final Class<?> cls) {
150     // To create a copy of a class we create a new child class loader with the same data as our
151     // parent and override loadClass to always load a new copy of cls.
152     try {
153       return new URLClassLoader(getClassPathUrls(), EagerSingletonTest.class.getClassLoader()) {
154         @Override
155         public Class<?> loadClass(String name) throws ClassNotFoundException {
156           // This means for every class besides cls we delegate to our parent (so things like
157           // @Singleton and Object are well defined), but for this one class we load a new one in
158           // this loader.
159           if (name.equals(cls.getName())) {
160             Class<?> c = findLoadedClass(name);
161             if (c == null) {
162               return super.findClass(name);
163             }
164             return c;
165           }
166           return super.loadClass(name);
167         }
168       }.loadClass(cls.getName());
169     } catch (ClassNotFoundException cnfe) {
170       throw new AssertionError(cnfe);
171     }
172   }
173 
174   @Singleton
175   static class A {
176     static int instanceCount = 0;
177     int instanceId = instanceCount++;
178 
179     @Inject
180     A(Injector injector) {
181       injector.getProvider(B.class);
182     }
183   }
184 
185   @Singleton
186   public static class B {
187     public static int instanceCount = 0;
188     int instanceId = instanceCount++;
189   }
190 
191   @Singleton
192   static class C implements D {
193     static int instanceCount = 0;
194     int instanceId = instanceCount++;
195   }
196 
197   private static interface D {}
198 }
199