/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.inject;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import junit.framework.TestCase;
/** @author jessewilson@google.com (Jesse Wilson) */
public class EagerSingletonTest extends TestCase {
@Override
public void setUp() {
A.instanceCount = 0;
B.instanceCount = 0;
C.instanceCount = 0;
}
public void testJustInTimeEagerSingletons() {
Guice.createInjector(
Stage.PRODUCTION,
new AbstractModule() {
@Override
protected void configure() {
// create a just-in-time binding for A
getProvider(A.class);
// create a just-in-time binding for C
requestInjection(
new Object() {
@Inject
void inject(Injector injector) {
injector.getInstance(C.class);
}
});
}
});
assertEquals(1, A.instanceCount);
assertEquals(
"Singletons discovered when creating singletons should not be built eagerly",
0,
B.instanceCount);
assertEquals(1, C.instanceCount);
}
public void testJustInTimeSingletonsAreNotEager() {
Injector injector = Guice.createInjector(Stage.PRODUCTION);
injector.getProvider(B.class);
assertEquals(0, B.instanceCount);
}
public void testChildEagerSingletons() {
Injector parent = Guice.createInjector(Stage.PRODUCTION);
parent.createChildInjector(
new AbstractModule() {
@Override
protected void configure() {
bind(D.class).to(C.class);
}
});
assertEquals(1, C.instanceCount);
}
// there used to be a bug that caused a concurrent modification exception if jit bindings were
// loaded during eager singleton creation due to failur to apply the lock when iterating over
// all bindings.
public void testJustInTimeEagerSingletons_multipleThreads() throws Exception {
// in order to make the data race more likely we need a lot of jit bindings. The easiest thing
// is just to 'copy' out class for B a bunch of times.
final List<Class<?>> jitBindings = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
jitBindings.add(copyClass(B.class));
}
Guice.createInjector(
Stage.PRODUCTION,
new AbstractModule() {
@Override
protected void configure() {
// create a just-in-time binding for A
getProvider(A.class);
// create a just-in-time binding for C
requestInjection(
new Object() {
@Inject
void inject(final Injector injector) throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
new Thread() {
@Override
public void run() {
latch.countDown();
for (Class<?> jitBinding : jitBindings) {
// this causes the binding to be created
injector.getProvider(jitBinding);
}
}
}.start();
latch.await(); // make sure the thread is running before returning
}
});
}
});
assertEquals(1, A.instanceCount);
// our thread runs in parallel with eager singleton loading so some there should be some number
// N such that 0<=N <jitBindings.size() and all classes in jitBindings with an index < N will
// have been initialized. Assert that!
int prev = -1;
int index = 0;
for (Class<?> jitBinding : jitBindings) {
int instanceCount = (Integer) jitBinding.getDeclaredField("instanceCount").get(null);
if (instanceCount != 0 && instanceCount != 1) {
fail("Should only have created zero or one instances, got " + instanceCount);
}
if (prev == -1) {
prev = instanceCount;
} else if (prev != instanceCount) {
if (prev != 1 && instanceCount != 0) {
fail("initialized later JIT bindings before earlier ones at index " + index);
}
prev = instanceCount;
}
index++;
}
}
/** Creates a copy of a class in a child classloader. */
private static Class<?> copyClass(final Class<?> cls) {
URLClassLoader parent = (URLClassLoader) EagerSingletonTest.class.getClassLoader();
// To create a copy of a class we create a new child class loader with the same data as our
// parent and override loadClass to always load a new copy of cls.
try {
return new URLClassLoader(parent.getURLs(), parent) {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// This means for every class besides cls we delegate to our parent (so things like
// @Singleton and Object are well defined), but for this one class we load a new one in
// this loader.
if (name.equals(cls.getName())) {
Class<?> c = findLoadedClass(name);
if (c == null) {
return super.findClass(name);
}
return c;
}
return super.loadClass(name);
}
}.loadClass(cls.getName());
} catch (ClassNotFoundException cnfe) {
throw new AssertionError(cnfe);
}
}
@Singleton
static class A {
static int instanceCount = 0;
int instanceId = instanceCount++;
@Inject
A(Injector injector) {
injector.getProvider(B.class);
}
}
@Singleton
public static class B {
public static int instanceCount = 0;
int instanceId = instanceCount++;
}
@Singleton
static class C implements D {
static int instanceCount = 0;
int instanceId = instanceCount++;
}
private static interface D {}
}