package com.netflix.governator;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.netflix.governator.annotations.SuppressLifecycleUninitialized;
import com.netflix.governator.spi.LifecycleListener;
/**
* Manage state for lifecycle listeners
*
* @author elandau
*/
@Singleton
@SuppressLifecycleUninitialized
public final class LifecycleManager {
private static final Logger LOG = LoggerFactory.getLogger(LifecycleManager.class);
/**
* Processes unreferenced LifecycleListeners from the referenceQueue, until
* the 'running' flag is false or interrupted
*
*/
private final class ListenerCleanupWorker implements Runnable {
public void run() {
try {
while (running.get()) {
Reference<? extends LifecycleListener> ref = unreferencedListenersQueue.remove(1000);
if (ref != null && ref instanceof SafeLifecycleListener) {
removeListener((SafeLifecycleListener)ref);
}
}
LOG.info("LifecycleManager.ListenerCleanupWorker is exiting");
}
catch (InterruptedException e) {
LOG.info("LifecycleManager.ListenerCleanupWorker is exiting due to thread interrupt");
}
}
}
private final Set<SafeLifecycleListener> listeners = new LinkedHashSet<>();
private final AtomicReference<State> state;
private final ReferenceQueue<LifecycleListener> unreferencedListenersQueue = new ReferenceQueue<>();
private volatile Throwable failureReason;
private final ExecutorService reqQueueExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("lifecycle-listener-monitor-%d").build());
private final AtomicBoolean running= new AtomicBoolean(true);
public enum State {
Starting,
Started,
Stopped,
Done
}
public LifecycleManager() {
LOG.info("Starting '{}'", this);
state = new AtomicReference<>(State.Starting);
reqQueueExecutor.submit(new ListenerCleanupWorker());
}
private synchronized void removeListener(SafeLifecycleListener listenerRef) {
listeners.remove(listenerRef);
}
public synchronized void addListener(LifecycleListener listener) {
SafeLifecycleListener safeListener = SafeLifecycleListener.wrap(listener, unreferencedListenersQueue);
if (!listeners.contains(safeListener) && listeners.add(safeListener)) {
LOG.info("Adding listener '{}'", safeListener);
switch (state.get()) {
case Started:
safeListener.onStarted();
break;
case Stopped:
safeListener.onStopped(failureReason);
break;
default:
// ignore
}
}
}
public synchronized void notifyStarted() {
if (state.compareAndSet(State.Starting, State.Started)) {
LOG.info("Started '{}'", this);
for (LifecycleListener listener : new ArrayList<>(listeners)) {
listener.onStarted();
}
}
}
public synchronized void notifyStartFailed(final Throwable t) {
// State.Started added here to allow for failure when LifecycleListener.onStarted() is called, post-injector creation
if (state.compareAndSet(State.Starting, State.Stopped) || state.compareAndSet(State.Started, State.Stopped)) {
LOG.info("Failed start of '{}'", this);
if (running.compareAndSet(true, false)) {
reqQueueExecutor.shutdown();
}
this.failureReason = t;
Iterator<SafeLifecycleListener> shutdownIter = new LinkedList<>(listeners).descendingIterator();
while (shutdownIter.hasNext()) {
shutdownIter.next().onStopped(t);
}
listeners.clear();
}
state.set(State.Done);
}
public synchronized void notifyShutdown() {
if (running.compareAndSet(true, false)) {
reqQueueExecutor.shutdown();
}
if (state.compareAndSet(State.Started, State.Stopped)) {
LOG.info("Stopping '{}'", this);
Iterator<SafeLifecycleListener> shutdownIter = new LinkedList<>(listeners).descendingIterator();
while (shutdownIter.hasNext()) {
shutdownIter.next().onStopped(null);
}
listeners.clear();
}
state.set(State.Done);
}
public State getState() {
return state.get();
}
public Throwable getFailureReason() {
return failureReason;
}
@Override
public String toString() {
return "LifecycleManager@" + System.identityHashCode(this);
}
}