package com.linkedin.databus.core.async;
/*
*
* 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.ArrayDeque;
import java.util.Iterator;
import java.util.Queue;
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.commons.collections.buffer.CircularFifoBuffer;
import org.apache.log4j.Logger;
import com.linkedin.databus.core.DatabusComponentStatus;
import com.linkedin.databus2.core.BackoffTimerStaticConfig;
/**
* A default implementation for an actor which let it be run in a thread and provides
* controls for its lifecycle.
*/
public abstract class AbstractActorMessageQueue implements Runnable, ActorMessageQueue
{
public static final String MODULE = AbstractActorMessageQueue.class.getName();
public final Logger _log;
public static final int MAX_QUEUED_MESSAGE_HISTORY_SIZE = 100;
public static final int MAX_QUEUED_MESSAGES = 10;
public static final long MESSAGE_QUEUE_POLL_TIMEOUT_MS = 100;
private final String _name;
private final Queue<Object> _messageQueue = new ArrayDeque<Object>(MAX_QUEUED_MESSAGES);
protected final CircularFifoBuffer _messageProcessedHistory = new CircularFifoBuffer(MAX_QUEUED_MESSAGE_HISTORY_SIZE);
volatile boolean _hasMessages;
private final Lock _controlLock = new ReentrantLock(true);
private final Condition _shutdownCondition = _controlLock.newCondition();
private final Condition _newStateCondition = _controlLock.newCondition();
private volatile LifecycleMessage _shutdownRequest = null;
protected LifecycleMessage _currentLifecycleState;
protected final DatabusComponentStatus _componentStatus;
private final MessageQueueFilter pauseFilter = new DefaultPauseFilter();
private final MessageQueueFilter suspendFilter = new DefaultSuspendFilter();
private final MessageQueueFilter shutdownFilter = new DefaultShutdownFilter();
private long _numEnqueuedMessages = 0;
private final boolean _enablePullerMessageQueueLogging;
public AbstractActorMessageQueue(String name, BackoffTimerStaticConfig errorRetriesConf)
{
this(name, errorRetriesConf,false,null);
}
public AbstractActorMessageQueue(String name,
BackoffTimerStaticConfig errorRetriesConf,
boolean enablePullerMessageQueueLogging,
Logger log)
{
super();
_name = name;
_currentLifecycleState = null;
_componentStatus = new DatabusComponentStatus(name, errorRetriesConf);
_enablePullerMessageQueueLogging = enablePullerMessageQueueLogging;
_hasMessages = false;
if (null != log)
{
_log = log;
}
else
{
// Should happen only for unit-tests
_log = Logger.getLogger(MODULE);
}
}
// Used only for unit-testing. Ideally moved to a factory method and reduce scope to
// be private. But there are unit tests which inherit from this class and require
// simple super() methods
protected AbstractActorMessageQueue(String name)
{
this(name,BackoffTimerStaticConfig.UNLIMITED_RETRIES);
}
/**
* This method is called by the StatMachine thread after it exits out of the main loop in AbstractActorMessageQueue.run() and
* is about to shutdown.
* After this method returns, the State Machine's status will be set to SHUTDOWN and other waiting threads (for this shutdown) will be signaled.
* Subclasses must implement this method to cleanup their state for shutdown
*/
protected abstract void onShutdown();
/**
* This method is called by the StatMachine thread after it exits out of the main loop in AbstractActorMessageQueue.run()
*/
private void performShutdown()
{
onShutdown();
_log.info(getName() + " shutdown.");
clearQueue(shutdownFilter);
}
protected void onResume()
{
}
public final boolean doExecuteAndChangeState(Object message)
{
boolean success = false;
try
{
success = executeAndChangeState(message);
} catch (RuntimeException re) {
_log.error("Stopping because of runtime exception :", re);
success = false;
}
_messageProcessedHistory.add(message);
_numEnqueuedMessages++;
if ( ! success )
{
_log.info("Message Queue History (earliest first) at end:" + getMessageHistoryLog());
} else if (_numEnqueuedMessages%MAX_QUEUED_MESSAGE_HISTORY_SIZE == 0) {
if (_enablePullerMessageQueueLogging)
{
_log.info("Message Queue History (earliest first) :" + getMessageHistoryLog());
} else if (_log.isDebugEnabled()) {
_log.debug("Message Queue History (earliest first) :" + getMessageHistoryLog());
}
}
return success;
}
protected boolean executeAndChangeState(Object message)
{
boolean success = true;
if (message instanceof LifecycleMessage)
{
LifecycleMessage lcMessage = (LifecycleMessage)message;
switch (lcMessage.getTypeId())
{
case START: doStart(lcMessage); break;
case PAUSE: doPause(lcMessage); break;
case SUSPEND_ON_ERROR: doSuspendOnError(lcMessage); break;
case RESUME: doResume(lcMessage); break;
case SHUTDOWN:
{
_log.error("Shutdown message is seen in the queue but not expected : Message :" + lcMessage);
success = false;
break;
}
default:
{
_log.error("Unknown Lifecycle message in RelayPullThread: " + lcMessage.getTypeId());
success = false;
break;
}
}
}
else
{
_log.error("Unknown message of type " + message.getClass().getName() + ": " + message.toString());
success = false;
}
return success;
}
protected void doResume(LifecycleMessage lcMessage)
{
_log.info(getName() + ": resuming");
_componentStatus.resume();
onResume();
}
protected void doSuspendOnError(LifecycleMessage lcMessage)
{
final Throwable lastError = lcMessage.getLastError();
if (null != lastError)
{
_log.info(getName() + ": suspending due to " + lastError, lastError);
}
else
{
_log.info(getName() + ": suspending");
}
if (_log.isDebugEnabled())
_log.debug(" because of message: " + lcMessage.getLastError());
_componentStatus.suspendOnError(lcMessage.getLastError());
clearQueue(suspendFilter);
}
protected void doPause(LifecycleMessage lcMessage)
{
_log.info(getName() + ": pausing");
_componentStatus.pause();
clearQueue(pauseFilter);
}
protected void doStart(LifecycleMessage lcMessage)
{
_log.info(getName() + ": starting");
_componentStatus.start();
}
@Override
public void run()
{
boolean isDebugEnabled = _log.isDebugEnabled();
Object nextState = null;
boolean running = true;
try
{
while (running && !checkForShutdownRequest())
{
nextState = pollNextState();
if (null == nextState)
{
running = false;
}
else
{
if (isDebugEnabled) _log.debug(getName() + ": new state: " + nextState.toString());
running = doExecuteAndChangeState(nextState);
}
}
}
catch (Exception e)
{
_log.error(getName() + ": stopping because of unhandled exception: ", e);
running = false;
}
if (isDebugEnabled)
{
StringBuilder sb = new StringBuilder(10240);
sb.append(getName());
sb.append(": message queue at exit:");
while (null != (nextState = _messageQueue.poll()))
{
sb.append(nextState.toString());
sb.append(' ');
}
_log.debug(sb.toString());
}
try
{
performShutdown();
} finally {
_controlLock.lock();
try
{
_componentStatus.shutdown();
_shutdownCondition.signalAll();
}
finally
{
_controlLock.unlock();
}
_log.info("Message Queue History (earliest first) at shutdown:" + getMessageHistoryLog());
if (isDebugEnabled) _log.debug(getName() + ": exited message loop.");
}
}
/*
* Atomically filters the message queue and enqueues the passed message
*/
public void enqueueMessageAfterFilter(Object message, MessageQueueFilter filter)
{
try
{
_controlLock.lock();
clearQueue(filter);
enqueueMessage(message);
} finally {
_controlLock.unlock();
}
}
/**
* Preprocess message before enqueueing
*
* @param message Message to be enqueued
* @return processed message
*/
protected Object preEnqueue(Object message)
{
return message;
}
@Override
public void enqueueMessage(Object message)
{
if (null == message)
{
_log.warn("Attempt to queue empty state");
return;
}
_controlLock.lock();
try
{
message = preEnqueue(message);
if (_componentStatus.getStatus() == DatabusComponentStatus.Status.SHUTDOWN)
{
_log.warn(getName() + ": shutdown: ignoring " + message.toString());
}
else if (checkForShutdownRequest())
{
_log.warn(getName() + ": shutdown requested: ignoring " + message.toString());
}
else if ((_componentStatus.getStatus() == DatabusComponentStatus.Status.PAUSED) &&
(! shouldRetainMessageOnPause(message)))
{
_log.warn(getName() + ": ignoring message while paused: " + message.toString());
}
else if ((_componentStatus.getStatus() == DatabusComponentStatus.Status.SUSPENDED_ON_ERROR) &&
(! shouldRetainMessageOnSuspend(message)))
{
_log.warn(getName() + ": ignoring message while suspended_on_error: " + message.toString());
}
else
{
boolean offerSuccess = _messageQueue.offer(message);
if (!offerSuccess) _log.error(getName() + ": adding a new state failed: " + message.toString()
+ "; queue.size=" + _messageQueue.size());
if (1 == _messageQueue.size()) _newStateCondition.signalAll();
_hasMessages = true;
}
// LOG.info(getName() + ": " + _messageQueue.toString());
}
finally
{
_controlLock.unlock();
}
}
public void shutdown()
{
_log.info(getName() + ": shutdown requested.");
_shutdownRequest = LifecycleMessage.createShutdownMessage();
}
public void awaitShutdown()
{
_log.info(getName() + ": waiting for shutdown" );
_controlLock.lock();
try
{
_log.info(getName() + ": status at shutdown: " + _componentStatus.getStatus());
_log.info(getName() + ": queue at shutdown: " + _messageQueue);
while (_componentStatus.getStatus() != DatabusComponentStatus.Status.SHUTDOWN &&
_componentStatus.getStatus() != DatabusComponentStatus.Status.INITIALIZING)
{
_shutdownCondition.awaitUninterruptibly();
}
}
finally
{
_controlLock.unlock();
}
}
public String getName()
{
return _name;
}
public boolean isShutdown()
{
_controlLock.lock();
try
{
return _componentStatus.getStatus() == DatabusComponentStatus.Status.SHUTDOWN;
}
finally
{
_controlLock.unlock();
}
}
private Object pollNextState()
{
Object nextState = null;
_controlLock.lock();
try
{
while (! checkForShutdownRequest() && _messageQueue.isEmpty())
{
try
{
_newStateCondition.await(MESSAGE_QUEUE_POLL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
catch (InterruptedException ie){}
}
if (! checkForShutdownRequest())
{
nextState = _messageQueue.poll();
_hasMessages = _messageQueue.size() > 0;
}
}
finally
{
_controlLock.unlock();
}
return nextState;
}
public DatabusComponentStatus getComponentStatus()
{
return _componentStatus;
}
public boolean checkForShutdownRequest()
{
return null != _shutdownRequest;
}
public String getQueueListString()
{
StringBuilder sb = new StringBuilder(100);
getQueueListString(sb);
return sb.toString();
}
public void getQueueListString(StringBuilder sb)
{
_controlLock.lock();
try
{
sb.append(getName());
sb.append(" queue: ");
sb.append(_messageQueue.toString());
}
finally
{
_controlLock.unlock();
}
}
protected void clearQueue(MessageQueueFilter filter)
{
try
{
_controlLock.lock();
Iterator<Object> itr = _messageQueue.iterator();
while (itr.hasNext())
{
Object msg = itr.next();
boolean retain = filter.shouldRetain(msg);
if (! retain)
itr.remove();
}
} finally {
_controlLock.unlock();
}
}
/**
* By default, all messages except lifecycle messages are cleared on pause
*/
protected void clearMessageQueueOnPause()
{
clearQueue(new DefaultPauseFilter());
}
/**
* By default, all messages except life-cycle messages are cleared on Suspend_On_Error
*/
protected void clearMessageQueueOnSuspend()
{
clearQueue(new DefaultSuspendFilter());
}
/**
* By default, all messages are cleared on shutdown
*/
protected void clearMessageQueueOnShutdown()
{
clearQueue(new DefaultShutdownFilter());
}
/**
* Filter Interface for clearing Message Queue
*/
public interface MessageQueueFilter
{
public boolean shouldRetain(Object msg);
}
/**
* Filter for clearing Message Queue on shutdown
*/
private class DefaultPauseFilter implements MessageQueueFilter
{
@Override
public boolean shouldRetain(Object msg)
{
return shouldRetainMessageOnPause(msg);
}
}
/**
* Filter for clearing Message Queue on Suspend_on_Error
*/
private class DefaultSuspendFilter implements MessageQueueFilter
{
@Override
public boolean shouldRetain(Object msg)
{
return shouldRetainMessageOnSuspend(msg);
}
}
/**
* Filter for clearing Message Queue on shutdown
*/
private class DefaultShutdownFilter implements MessageQueueFilter
{
@Override
public boolean shouldRetain(Object msg) {
return shouldRetainMessageOnShutdown(msg);
}
}
/**
* By default, all messages except lifecycle messages are cleared on pause
*/
protected boolean shouldRetainMessageOnPause(Object msg)
{
if ( msg instanceof LifecycleMessage)
return true;
return false;
}
/**
* By default, all messages except lifecycle messages are cleared on suspend
*/
protected boolean shouldRetainMessageOnSuspend(Object msg)
{
if ( msg instanceof LifecycleMessage)
return true;
return false;
}
/**
* By default, all messages are cleared on shutdown
*/
protected boolean shouldRetainMessageOnShutdown(Object msg)
{
return false;
}
public Queue<Object> getMessageQueue()
{
return _messageQueue;
}
public String getMessageHistoryLog()
{
return _messageProcessedHistory.toString();
}
protected boolean hasMessages()
{
return _hasMessages;
}
}