package com.netflix.governator.guice.concurrent;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.List;
import java.util.Set;
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.TimeUnit;
import com.google.common.base.Supplier;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable;
import com.netflix.governator.annotations.NonConcurrent;
import com.netflix.governator.lifecycle.LifecycleListener;
/**
* Utility class for creating Providers that allow for concurrent instantiation
* of dependencies to a type.
*
* @author elandau
*
*/
public class ConcurrentProviders {
/**
* Create a Provider that will construct all constructor arguments in parallel and wait
* for all dependencies to be constructed before invoking the constructor of the type.
*
* For example, consider the following class that has 4 dependencies
*
* {@code
* @Singleton
* public class Foo {
* @Inject
* public Foo(@NonConcurrent NonConcurrentSingleton, DependencyA a, DependencyB b, Provider<DependencyC> c, NonSingletonD d) {
* }
* }
* }
*
* and the following Guice binding to enable the concurrent behavior,
*
* {@code
* public configure() {
* bind(Foo.class).toProvider(ConcurrentProviders.of(Foo.class)).asEagerSingleton();
* }
* }
*
* When Foo is created eagerly (by Guice) the provider will spawn 4 threads each creating
* one of the above dependencies. Note that for Provider<DependencyC> the provider will
* be created and not an instance of DependencyC. Also, note that NonConcurrentSingleton
* will not be constructed in a separate thread.
*
* Note that a dedicated pool of N threads (where N is the number of dependencies) is created
* when Foo is first constructed. Upon instantiation of Foo the pool is shut down and the
* resulting instance of Foo cached for future retrieval.
*
* It's also important to note that ALL transitive dependencies of Foo MUST be in the
* <b>FineGrainedLazySingleton</b> scope, otherwise there is a high risk of hitting the global Guice
* Singleton scope deadlock issue. Any parameter that causes this deadlock can be annotated
* with @NonConcurrent to force it to be created within the same thread as the injectee.
*
* @param type
* @return
*/
public static <T> Provider<T> of(final Class<? extends T> type) {
return new ProviderWithExtensionVisitor<T>() {
private volatile T instance;
private Injector injector;
private Set<LifecycleListener> listeners = Collections.emptySet();
public T get() {
if ( instance == null ) {
synchronized (this) {
if ( instance == null ) {
instance = createAndInjectMember();
}
}
}
return instance;
}
private T createAndInjectMember() {
T instance = create();
injector.injectMembers(instance);
return instance;
}
private T create() {
// Look for an @Inject constructor or just create a new instance if not found
InjectionPoint injectionPoint = InjectionPoint.forConstructorOf(type);
final long startTime = System.nanoTime();
for (LifecycleListener listener : listeners) {
listener.objectInjecting(TypeLiteral.get(type));
}
if (injectionPoint != null) {
List<Dependency<?>> deps = injectionPoint.getDependencies();
if (deps.size() > 0) {
Constructor<?> constructor = (Constructor<?>)injectionPoint.getMember();
// One thread for each dependency
ExecutorService executor = Executors.newCachedThreadPool(
new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("ConcurrentProviders-" + type.getSimpleName() + "-%d")
.build());
try {
List<Supplier<?>> suppliers = Lists.newArrayListWithCapacity(deps.size());
// Iterate all constructor dependencies and get and instance from the Injector
for (final Dependency<?> dep : deps) {
if (!isConcurrent(constructor, dep.getParameterIndex())) {
suppliers.add(getCreator(dep.getKey()));
}
else {
final Future<?> future = executor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
return getCreator(dep.getKey()).get();
}
});
suppliers.add(new Supplier() {
@Override
public Object get() {
try {
return future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ProvisionException("interrupted during provision");
} catch (ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
});
}
}
// All dependencies are now being instantiated in parallel
// Fetch the arguments from the futures and put in an array to pass to newInstance
List<Object> params = Lists.newArrayListWithCapacity(deps.size());
for (Supplier<?> supplier: suppliers) {
params.add(supplier.get());
}
// All dependencies have been initialized
// Look for the @Inject constructor and invoke it.
try {
T obj = (T)constructor.newInstance(params.toArray());
long duration = System.nanoTime() - startTime;
for (LifecycleListener listener : listeners) {
listener.objectInjected((TypeLiteral<T>)TypeLiteral.get(type), obj, duration, TimeUnit.NANOSECONDS);
}
return obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
finally {
executor.shutdown();
}
}
}
try {
T obj = type.newInstance();
long duration = System.nanoTime() - startTime;
for (LifecycleListener listener : listeners) {
listener.objectInjected((TypeLiteral<T>)TypeLiteral.get(type), obj, duration, TimeUnit.NANOSECONDS);
}
return obj;
} catch (Exception e) {
e.printStackTrace();
throw new ProvisionException("Error constructing object of type " + type.getName(), e);
}
}
private boolean isConcurrent(Constructor<?> constructor, int parameterIndex) {
Annotation[] annots = constructor.getParameterAnnotations()[parameterIndex];
if (annots != null) {
for (Annotation annot : annots) {
if (annot.annotationType().equals(NonConcurrent.class)) {
return false;
}
}
}
return true;
}
/**
* Required to get the Injector in {@link initialize()}
*/
@Override
public <B, V> V acceptExtensionVisitor(
BindingTargetVisitor<B, V> visitor,
ProviderInstanceBinding<? extends B> binding) {
return visitor.visit(binding);
}
@Inject
@Toolable
void initialize(Injector injector) {
this.injector = injector;
}
@Inject(optional = true)
void setListeners(Set<LifecycleListener> listeners) {
this.listeners = listeners;
}
public <S> Supplier<S> getCreator(final Key<S> key) {
return new Supplier<S>() {
@Override
public S get() {
final long startTime = System.nanoTime();
for (LifecycleListener listener : listeners) {
listener.objectInjecting(key.getTypeLiteral());
}
S obj = injector.getInstance(key);
final long duration = System.nanoTime() - startTime;
for (LifecycleListener listener : listeners) {
listener.objectInjected(key.getTypeLiteral(), obj, System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
}
return obj;
}
};
}
};
}
}