package org.jactr.tools.async.sync;
/*
* default logging
*/
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.commonreality.net.handler.IMessageHandler;
import org.commonreality.net.session.ISessionInfo;
import org.jactr.core.concurrent.ExecutorServices;
import org.jactr.core.model.IModel;
import org.jactr.core.queue.timedevents.RunnableTimedEvent;
import org.jactr.core.runtime.ACTRRuntime;
import org.jactr.core.runtime.event.ACTRRuntimeAdapter;
import org.jactr.core.runtime.event.ACTRRuntimeEvent;
import org.jactr.core.runtime.event.IACTRRuntimeListener;
import org.jactr.core.utils.parameter.IParameterized;
import org.jactr.core.utils.parameter.ParameterHandler;
import org.jactr.instrument.IInstrument;
import org.jactr.tools.async.controller.RemoteInterface;
import org.jactr.tools.misc.ModelsLock;
/**
* The wonder of asynchronous messaging is that you don't have to wait, giving
* the runtime greater throughput. Unfortunately, it can get too fast - drowning
* the other side. This allows us to specify the frequency of synchronizations.
* If we could detect load, it'd be even better.. <br/>
*
* @bug we should time out for waiting for the synchronization reply.
* @author harrison
*/
public class SynchronizationManager implements IInstrument, IParameterized
{
/**
* Logger definition
*/
static private final transient Log LOGGER = LogFactory
.getLog(SynchronizationManager.class);
// static public final String SYNC_AT_START = "SynchronizeOnStartUp";
static public final String INTERVAL = "SynchronizationDelayInMS";
private ModelsLock _modelsLock;
private double _delay = 30; // s
private Runnable _blockProcessor;
private Runnable _messageProcessor;
private SynchronizationMessage _message;
private IACTRRuntimeListener _runtimeListener;
public SynchronizationManager()
{
_modelsLock = new ModelsLock();
_modelsLock.initialize();
}
synchronized public void initialize()
{
if (_runtimeListener == null)
{
_runtimeListener = new ACTRRuntimeAdapter() {
@Override
public void runtimeStarted(ACTRRuntimeEvent event)
{
// do the real work
if (_blockProcessor == null && _messageProcessor == null)
{
/*
* try the interface, which we need for catching the sync message
*/
RemoteInterface ri = RemoteInterface.getActiveRemoteInterface();
if (ri == null)
LOGGER
.warn(String
.format(
"%s requires the RemoteInterface to be running, will not attempt to synchronize",
getClass().getName()));
else
{
/*
* install our handler into the active session (if connected) or
* the default handler set
*/
IMessageHandler<SynchronizationMessage> handler = (s, m) -> synchronizationPointReached(m);
ISessionInfo session = ri.getActiveSession();
if (session != null)
session.addHandler(SynchronizationMessage.class, handler);
else
ri.getDefaultHandlers().put(SynchronizationMessage.class,
handler);
}
_blockProcessor = new Runnable() {
public void run()
{
synchronize();
}
};
_messageProcessor = new Runnable() {
public void run()
{
try
{
RemoteInterface.getActiveRemoteInterface().getActiveSession()
.write(_message);
}
catch (Exception e)
{
// TODO Auto-generated catch block
LOGGER.error(".run threw Exception : ", e);
}
}
};
//
scheduleProcess();
}
}
};
ACTRRuntime.getRuntime().addListener(_runtimeListener, null);
}
}
public void install(IModel model)
{
_modelsLock.install(model);
}
public void uninstall(IModel model)
{
// this will stop the processor from rescheduling
_blockProcessor = null;
ACTRRuntime.getRuntime().removeListener(_runtimeListener);
_modelsLock.uninstall(model);
}
public String getParameter(String key)
{
// meh, too lazy.
return null;
}
public Collection<String> getPossibleParameters()
{
return getSetableParameters();
}
public Collection<String> getSetableParameters()
{
return Arrays.asList(/* SYNC_AT_START, */INTERVAL);
}
public void setParameter(String key, String value)
{
// if (SYNC_AT_START.equalsIgnoreCase(key))
// _syncAtStart = ParameterHandler.booleanInstance().coerce(value)
// .booleanValue();
// else
if (INTERVAL.equalsIgnoreCase(key))
_delay = ParameterHandler.numberInstance().coerce(value).doubleValue() / 1000.0;
}
protected void scheduleProcess()
{
if (_blockProcessor != null
&& RemoteInterface.getActiveRemoteInterface() != null)
/*
* now we just pick a model and attach to it. If that model is removed, we
* reschedule
*/
try
{
IModel model = ACTRRuntime.getRuntime().getController()
.getRunningModels().iterator().next();
double when = model.getAge() + _delay;
RunnableTimedEvent rte = new RunnableTimedEvent(when, _blockProcessor);
model.getTimedEventQueue().enqueue(rte);
}
catch (Exception e)
{
LOGGER.debug("No model to attach to, irrelevant ", e);
}
}
synchronized protected void synchronize()
{
if (_message != null)
{
// message is nulled when we wake back up?
// redundant, we already are
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("We are already synchronizing"));
return;
}
if (!_modelsLock.isClosed() && _modelsLock.areAllFree())
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("Attempting to synchronize"));
// signal
CompletableFuture<Boolean> closeFuture = _modelsLock.close();
// send out the message when all have reached the synch point.
_message = new SynchronizationMessage();
closeFuture.thenRunAsync(_messageProcessor,
ExecutorServices.getExecutor(ExecutorServices.BACKGROUND));
// try
// {
// /*
// * wait until everyone has blocked and then send the message.
// */
// if (LOGGER.isDebugEnabled())
// LOGGER.debug(String.format("Attempting to get result of close"));
//
// closeFuture.get(1000, TimeUnit.MILLISECONDS);
//
// if (LOGGER.isDebugEnabled())
// LOGGER.debug(String.format("Sending message"));
// /*
// * we send the message on the back ground thread since it is the thread
// * that likely has all the pending requests on it. If we sent from here,
// * there synch message would arrive before all the pending messages on
// * the background thread, negating the intended purpose
// */
// Executor executor = ExecutorServices
// .getExecutor(ExecutorServices.BACKGROUND);
// if (executor != null) executor.execute(_messageProcessor);
//
// }
// catch (TimeoutException e)
// {
// LOGGER
// .error("Waiting for all models to close took too long, stumbling forward");
// _message = null;
// }
// catch (Exception e)
// {
// LOGGER
// .error(
// "SynchronizationManager.synchronize threw InterruptedException. Aborting synchronization",
// e);
//
// _message = null;
// }
}
else
{
/*
* some are blocked? that's weird, lets reschedule
*/
if (LOGGER.isWarnEnabled())
LOGGER
.warn(String
.format("Deferring synchronization request until all models are free"));
scheduleProcess();
}
}
protected void synchronizationPointReached(SynchronizationMessage reply)
{
if (_message == null)
{
LOGGER.warn("Reply received, but we didn't send the request. WTF?");
return;
}
if (reply.inResponseTo() != _message.getID())
{
LOGGER.warn("WTF? reply was not to recently sent request.");
return;
}
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format(
"Synchronized, releasing the hounds. Synchronization took %d ms",
reply.getTimestamp() - _message.getTimestamp()));
_modelsLock.open();
_message = null;
/*
* and once more..
*/
scheduleProcess();
}
}