All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.druid.java.util.common.lifecycle.Lifecycle Maven / Gradle / Ivy

There is a newer version: 0.12.3
Show newest version
/*
 * Licensed to Metamarkets Group Inc. (Metamarkets) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. Metamarkets licenses this file
 * to you 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 io.druid.java.util.common.lifecycle;

import com.google.common.collect.Lists;
import io.druid.java.util.common.ISE;
import io.druid.java.util.common.logger.Logger;

import java.io.Closeable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A manager of object Lifecycles.
 * 

* This object has methods for registering objects that should be started and stopped. The Lifecycle allows for * two stages: Stage.NORMAL and Stage.LAST. *

* Things added at Stage.NORMAL will be started first (in the order that they are added to the Lifecycle instance) and * then things added at Stage.LAST will be started. *

* The close operation goes in reverse order, starting with the last thing added at Stage.LAST and working backwards. *

* There are two sets of methods to add things to the Lifecycle. One set that will just add instances and enforce that * start() has not been called yet. The other set will add instances and, if the lifecycle is already started, start * them. */ public class Lifecycle { private static final Logger log = new Logger(Lifecycle.class); public enum Stage { NORMAL, LAST } private enum State { /** Lifecycle's state before {@link #start()} is called. */ NOT_STARTED, /** Lifecycle's state since {@link #start()} and before {@link #stop()} is called. */ RUNNING, /** Lifecycle's state since {@link #stop()} is called. */ STOP } private final NavigableMap> handlers; /** This lock is used to linearize all calls to Handler.start() and Handler.stop() on the managed handlers. */ private final Lock startStopLock = new ReentrantLock(); private final AtomicReference state = new AtomicReference<>(State.NOT_STARTED); private Stage currStage = null; private final AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false); public Lifecycle() { handlers = new TreeMap<>(); for (Stage stage : Stage.values()) { handlers.put(stage, new CopyOnWriteArrayList<>()); } } /** * Adds a "managed" instance (annotated with {@link LifecycleStart} and {@link LifecycleStop}) to the Lifecycle at * Stage.NORMAL. If the lifecycle has already been started, it throws an {@link ISE} * * @param o The object to add to the lifecycle * * @throws ISE {@link Lifecycle#addHandler(Handler, Stage)} */ public T addManagedInstance(T o) { addHandler(new AnnotationBasedHandler(o)); return o; } /** * Adds a "managed" instance (annotated with {@link LifecycleStart} and {@link LifecycleStop}) to the Lifecycle. * If the lifecycle has already been started, it throws an {@link ISE} * * @param o The object to add to the lifecycle * @param stage The stage to add the lifecycle at * * @throws ISE {@link Lifecycle#addHandler(Handler, Stage)} */ public T addManagedInstance(T o, Stage stage) { addHandler(new AnnotationBasedHandler(o), stage); return o; } /** * Adds an instance with a start() and/or close() method to the Lifecycle at Stage.NORMAL. If the lifecycle has * already been started, it throws an {@link ISE} * * @param o The object to add to the lifecycle * * @throws ISE {@link Lifecycle#addHandler(Handler, Stage)} */ public T addStartCloseInstance(T o) { addHandler(new StartCloseHandler(o)); return o; } /** * Adds an instance with a start() and/or close() method to the Lifecycle. If the lifecycle has already been started, * it throws an {@link ISE} * * @param o The object to add to the lifecycle * @param stage The stage to add the lifecycle at * * @throws ISE {@link Lifecycle#addHandler(Handler, Stage)} */ public T addStartCloseInstance(T o, Stage stage) { addHandler(new StartCloseHandler(o), stage); return o; } /** * Adds a handler to the Lifecycle at the Stage.NORMAL stage. If the lifecycle has already been started, it throws * an {@link ISE} * * @param handler The hander to add to the lifecycle * * @throws ISE {@link Lifecycle#addHandler(Handler, Stage)} */ public void addHandler(Handler handler) { addHandler(handler, Stage.NORMAL); } /** * Adds a handler to the Lifecycle. If the lifecycle has already been started, it throws an {@link ISE} * * @param handler The hander to add to the lifecycle * @param stage The stage to add the lifecycle at * * @throws ISE indicates that the lifecycle has already been started and thus cannot be added to */ public void addHandler(Handler handler, Stage stage) { if (!startStopLock.tryLock()) { throw new ISE("Cannot add a handler in the process of Lifecycle starting or stopping"); } try { if (!state.get().equals(State.NOT_STARTED)) { throw new ISE("Cannot add a handler after the Lifecycle has started, it doesn't work that way."); } handlers.get(stage).add(handler); } finally { startStopLock.unlock(); } } /** * Adds a "managed" instance (annotated with {@link LifecycleStart} and {@link LifecycleStop}) to the Lifecycle at * Stage.NORMAL and starts it if the lifecycle has already been started. * * @param o The object to add to the lifecycle * * @throws Exception {@link Lifecycle#addMaybeStartHandler(Handler, Stage)} */ public T addMaybeStartManagedInstance(T o) throws Exception { addMaybeStartHandler(new AnnotationBasedHandler(o)); return o; } /** * Adds a "managed" instance (annotated with {@link LifecycleStart} and {@link LifecycleStop}) to the Lifecycle * and starts it if the lifecycle has already been started. * * @param o The object to add to the lifecycle * @param stage The stage to add the lifecycle at * * @throws Exception {@link Lifecycle#addMaybeStartHandler(Handler, Stage)} */ public T addMaybeStartManagedInstance(T o, Stage stage) throws Exception { addMaybeStartHandler(new AnnotationBasedHandler(o), stage); return o; } /** * Adds an instance with a start() and/or close() method to the Lifecycle at Stage.NORMAL and starts it if the * lifecycle has already been started. * * @param o The object to add to the lifecycle * * @throws Exception {@link Lifecycle#addMaybeStartHandler(Handler, Stage)} */ public T addMaybeStartStartCloseInstance(T o) throws Exception { addMaybeStartHandler(new StartCloseHandler(o)); return o; } /** * Adds an instance with a start() and/or close() method to the Lifecycle and starts it if the lifecycle has * already been started. * * @param o The object to add to the lifecycle * @param stage The stage to add the lifecycle at * * @throws Exception {@link Lifecycle#addMaybeStartHandler(Handler, Stage)} */ public T addMaybeStartStartCloseInstance(T o, Stage stage) throws Exception { addMaybeStartHandler(new StartCloseHandler(o), stage); return o; } /** * Adds a Closeable instance to the lifecycle at {@link Stage#NORMAL} stage, doesn't try to call any "start" method on * it, use {@link #addStartCloseInstance(Object)} instead if you need the latter behaviour. */ public T addCloseableInstance(T o) { addHandler(new CloseableHandler(o)); return o; } /** * Adds a handler to the Lifecycle at the Stage.NORMAL stage and starts it if the lifecycle has already been started. * * @param handler The hander to add to the lifecycle * * @throws Exception {@link Lifecycle#addMaybeStartHandler(Handler, Stage)} */ public void addMaybeStartHandler(Handler handler) throws Exception { addMaybeStartHandler(handler, Stage.NORMAL); } /** * Adds a handler to the Lifecycle and starts it if the lifecycle has already been started. * * @param handler The hander to add to the lifecycle * @param stage The stage to add the lifecycle at * * @throws Exception an exception thrown from handler.start(). If an exception is thrown, the handler is *not* added */ public void addMaybeStartHandler(Handler handler, Stage stage) throws Exception { if (!startStopLock.tryLock()) { // (*) This check is why the state should be changed before startStopLock.lock() in stop(). This check allows to // spot wrong use of Lifecycle instead of entering deadlock, like https://github.com/druid-io/druid/issues/3579. if (state.get().equals(State.STOP)) { throw new ISE("Cannot add a handler in the process of Lifecycle stopping"); } startStopLock.lock(); } try { if (state.get().equals(State.STOP)) { throw new ISE("Cannot add a handler after the Lifecycle has stopped"); } if (state.get().equals(State.RUNNING)) { if (stage.compareTo(currStage) <= 0) { handler.start(); } } handlers.get(stage).add(handler); } finally { startStopLock.unlock(); } } public void start() throws Exception { startStopLock.lock(); try { if (!state.get().equals(State.NOT_STARTED)) { throw new ISE("Already started"); } if (!state.compareAndSet(State.NOT_STARTED, State.RUNNING)) { throw new ISE("stop() is called concurrently with start()"); } for (Map.Entry> e : handlers.entrySet()) { currStage = e.getKey(); for (Handler handler : e.getValue()) { handler.start(); } } } finally { startStopLock.unlock(); } } public void stop() { // This CAS outside of a block guarded by startStopLock is the only reason why state is AtomicReference rather than // a simple variable. State change before startStopLock.lock() is needed for the new state visibility during the // check in addMaybeStartHandler() marked by (*). if (!state.compareAndSet(State.RUNNING, State.STOP)) { log.info("Already stopped and stop was called. Silently skipping"); return; } startStopLock.lock(); try { RuntimeException thrown = null; for (List stageHandlers : handlers.descendingMap().values()) { for (Handler handler : Lists.reverse(stageHandlers)) { try { handler.stop(); } catch (RuntimeException e) { log.warn(e, "exception thrown when stopping %s", handler); if (thrown == null) { thrown = e; } } } } if (thrown != null) { throw thrown; } } finally { startStopLock.unlock(); } } public void ensureShutdownHook() { if (shutdownHookRegistered.compareAndSet(false, true)) { Runtime.getRuntime().addShutdownHook( new Thread( new Runnable() { @Override public void run() { log.info("Running shutdown hook"); stop(); } } ) ); } } public void join() throws InterruptedException { ensureShutdownHook(); Thread.currentThread().join(); } public interface Handler { void start() throws Exception; void stop(); } private static class AnnotationBasedHandler implements Handler { private static final Logger log = new Logger(AnnotationBasedHandler.class); private final Object o; public AnnotationBasedHandler(Object o) { this.o = o; } @Override public void start() throws Exception { for (Method method : o.getClass().getMethods()) { boolean doStart = false; for (Annotation annotation : method.getAnnotations()) { if (annotation.annotationType() .getCanonicalName() .equals("io.druid.java.util.common.lifecycle.LifecycleStart")) { doStart = true; break; } } if (doStart) { log.info("Invoking start method[%s] on object[%s].", method, o); method.invoke(o); } } } @Override public void stop() { for (Method method : o.getClass().getMethods()) { boolean doStop = false; for (Annotation annotation : method.getAnnotations()) { if (annotation.annotationType() .getCanonicalName() .equals("io.druid.java.util.common.lifecycle.LifecycleStop")) { doStop = true; break; } } if (doStop) { log.info("Invoking stop method[%s] on object[%s].", method, o); try { method.invoke(o); } catch (Exception e) { log.error(e, "Exception when stopping method[%s] on object[%s]", method, o); } } } } } private static class StartCloseHandler implements Handler { private static final Logger log = new Logger(StartCloseHandler.class); private final Object o; private final Method startMethod; private final Method stopMethod; public StartCloseHandler(Object o) { this.o = o; try { startMethod = o.getClass().getMethod("start"); stopMethod = o.getClass().getMethod("close"); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } @Override public void start() throws Exception { log.info("Starting object[%s]", o); startMethod.invoke(o); } @Override public void stop() { log.info("Stopping object[%s]", o); try { stopMethod.invoke(o); } catch (Exception e) { log.error(e, "Unable to invoke stopMethod() on %s", o.getClass()); } } } private static class CloseableHandler implements Handler { private static final Logger log = new Logger(CloseableHandler.class); private final Closeable o; private CloseableHandler(Closeable o) { this.o = o; } @Override public void start() throws Exception { // do nothing } @Override public void stop() { log.info("Closing object[%s]", o); try { o.close(); } catch (Exception e) { log.error(e, "Exception when closing object [%s]", o); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy