package com.linkedin.databus.core; /* * * Copyright 2013 LinkedIn Corp. All rights reserved * * 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. * */ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.log4j.Logger; import com.linkedin.databus2.core.DatabusException; /** * * Base Class for implementing any thread of control which needs functionalities to pause,resume and shutdown in a thread-safe manner. * * The derived thread implementation classes are expected to override the following methods. * * {@link beforeRun()} * {@link runOnce()} * {@link afterRun()} * * */ public abstract class DatabusThreadBase extends Thread { protected final Logger _log; protected Lock _controlLock = new ReentrantLock(true); protected Condition _shutdownCondition = _controlLock.newCondition(); protected Condition _pauseCondition = _controlLock.newCondition(); protected Condition _resumeCondition = _controlLock.newCondition(); protected Condition _resumeRequestCondition = _controlLock.newCondition(); protected boolean _shutdownRequested = false; protected boolean _shutdown = false; protected boolean _pauseRequested = false; protected boolean _paused = false; protected boolean _resumeRequested = false; public static final String MODULE = DatabusThreadBase.class.getName(); public static final Logger LOG = Logger.getLogger(MODULE); public DatabusThreadBase(String name) { super(name); setDaemon(true); _log = Logger.getLogger(getClass().getName() + "." + name); } /** * Provides framework to run the user-defined method {@link runOnce()} continuously * until paused/shutdown. */ public void run() { try { beforeRun(); boolean done = false; while ( (!done) && (!isShutdownRequested())) { while (isPauseRequested()) { LOG.info("Pausing !!"); signalPause(); try { // wait for resume awaitUnPauseRequest(); LOG.info("Resuming !!"); } catch(InterruptedException ie) {} } done = ! runOnce(); } LOG.info("Shutting down !!"); doShutdownNotify(); afterRun(); } catch (DatabusException ex) { LOG.error("Got error. Stopping !! ", ex); } } /** * This is the method that subclasses are supposed to override. * Currently the method has default implementation. * * TODO: Classes like BootstrapApplierThread and Cleaner classes needs * to be moved to this framework and runOnce() should be marked abstract. * * @return true if the framework has to continue calling this method again or false to exit the loop and shutdown * @throws DatabusException if encountered any fatal errors that requires shutting down this thread. * */ public boolean runOnce() throws DatabusException {return false;} /** * This is the method that subclasses are supposed to override. Called before * runOnce() is called. * Currently the method has default implementation. * * TODO: Classes like BootstrapApplierThread and Cleaner classes needs * to be moved to this framework and runOnce() should be marked abstract. * @throws DatabusException if encountered any fatal errors that requires shutting down this thread. */ public void beforeRun() throws DatabusException {} /** * This is the method that subclasses are supposed to override. * Called after shutdown notification happens in {@link run()} * Currently the method has default implementation. * * TODO: Classes like BootstrapApplierThread and Cleaner classes needs * to be moved to this framework and runOnce() should be marked abstract. * @throws DatabusException if encountered any fatal errors that requires shutting down this thread. */ public void afterRun() throws DatabusException {} public void pauseAsynchronously() { _log.info("Pause requested"); _controlLock.lock(); try { _pauseRequested = true; } finally { _controlLock.unlock(); } } public void pause() throws InterruptedException { pauseAsynchronously(); awaitPause(); } public void unpauseAsynchronously() { _log.info("Resume requested"); _controlLock.lock(); try { _resumeRequested = true; _resumeRequestCondition.signal(); } finally { _controlLock.unlock(); } } public void unpause() throws InterruptedException { unpauseAsynchronously(); awaitUnPause(); } public void signalPause() { _controlLock.lock(); try { _paused = true; _pauseCondition.signal(); } finally { _controlLock.unlock(); } } /** * Notify that this thread has resumed. */ protected void signalResumed() { _controlLock.lock(); try { _paused = false; _resumeRequested = true; _resumeCondition.signal(); } finally { _controlLock.unlock(); } } public void shutdownAsynchronously() { _log.info("Shutdown requested"); _controlLock.lock(); try { _shutdownRequested = true; } finally { _controlLock.unlock(); } } public void shutdown() { shutdownAsynchronously(); awaitShutdownUniteruptibly(); } public boolean isPauseRequested() { _controlLock.lock(); try { return _pauseRequested; } finally { _controlLock.unlock(); } } public boolean isPaused() { _controlLock.lock(); try { return _paused; } finally { _controlLock.unlock(); } } public boolean isUnPauseRequested() { _controlLock.lock(); try { return _resumeRequested; } finally { _controlLock.unlock(); } } public boolean isUnPaused() { _controlLock.lock(); try { return ! _paused; } finally { _controlLock.unlock(); } } public boolean isShutdownRequested() { _controlLock.lock(); try { return _shutdownRequested; } finally { _controlLock.unlock(); } } public boolean isShutdown() { _controlLock.lock(); try { return _shutdownRequested; } finally { _controlLock.unlock(); } } /** Awaits interruptibly for the thread to pause */ public void awaitPause() throws InterruptedException { _log.info("Waiting to be paused"); _controlLock.lock(); try { while (! _paused) _pauseCondition.await(); _pauseRequested = false; } finally { _controlLock.unlock(); } _log.info("Paused: true"); } /** Awaits interruptibly for the thread to unpause */ public void awaitUnPause() throws InterruptedException { _log.info("Waiting for resumption"); _controlLock.lock(); try { while (_paused) _resumeCondition.await(); _resumeRequested = false; } finally { _controlLock.unlock(); } _log.info("Resumed: true"); } /** Awaits interruptibly for the thread to be unpause */ protected void awaitUnPauseRequest() throws InterruptedException { _log.info("Waiting to be requested for resume"); _controlLock.lock(); try { while (!_resumeRequested) _resumeRequestCondition.await(); } finally { _controlLock.unlock(); } _log.info("Resume Requested: true"); } /** * Awaits interruptibly for the thread to pause or until time out. * @return true if the pause happened and false if there was a time out * */ public boolean awaitPause(long timeout, TimeUnit timeUnit) throws InterruptedException { _log.info("Waiting for pause with timeout"); boolean success; _controlLock.lock(); try { while (! _paused) { boolean successfulWait = _pauseCondition.await(timeout, timeUnit); if (_log.isDebugEnabled()) _log.debug("Await Condition returned :" + successfulWait); } success = _paused; } finally { _controlLock.unlock(); } _log.info("Paused: " + success); return success; } /** * Awaits interruptibly for the thread to resume or until time out. * @return true if the resume happened and false if there was a time out * */ public boolean awaitUnPause(long timeout, TimeUnit timeUnit) throws InterruptedException { _log.info("Waiting for resume with timeout"); boolean success; _controlLock.lock(); try { while (_paused) { boolean successfulWait = _resumeCondition.await(timeout, timeUnit); if (_log.isDebugEnabled()) _log.debug("Await Condition returned :" + successfulWait); } success = !_paused; } finally { _controlLock.unlock(); } _log.info("UnPaused: " + success); return success; } /** Awaits interruptibly for the thread to shutdown */ public void awaitShutdown() throws InterruptedException { _log.info("Waiting for shutdown"); _controlLock.lock(); try { while (! _shutdown) _shutdownCondition.await(); } finally { _controlLock.unlock(); } _log.info("Shutdown: true"); } /** Awaits interruptibly for the thread to shutdown */ public void awaitShutdownUniteruptibly() { _log.info("Waiting for shutdown uninteruptibly"); boolean keepOnWaiting = true; while (keepOnWaiting) { try { awaitShutdown(); keepOnWaiting = false; } catch (InterruptedException ie) {} } _log.info("Shutdown: true"); } /** * Awaits interruptibly for the thread to shutdown or until time out. * @return true if the shutdown happened and false if there was a time out * */ public boolean awaitShutdownUninteruptibly(long timeout, TimeUnit timeUnit) { _log.info("Waiting for shutdown uninteruptibly with timeout"); boolean success; _controlLock.lock(); try { long startTime = System.nanoTime(); long timeoutNanos = timeUnit.toNanos(timeout); while (! _shutdown) { try { long elapsed = (System.nanoTime() - startTime); if (elapsed >= timeoutNanos) break; success = _shutdownCondition.await(timeoutNanos - elapsed, timeUnit); } catch (InterruptedException ie){} } success = _shutdown; } finally { _controlLock.unlock(); } _log.info("Shutdown: " + success); return success; } /** * Caller is notifying that it is shutting down */ protected void doShutdownNotify() { _controlLock.lock(); try { _log.info("Signalling shutdown !!"); _shutdown = true; _shutdownCondition.signalAll(); } finally { _controlLock.unlock(); } } }