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