package org.jactr.tools.misc;
/*
* default logging
*/
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jactr.core.model.IModel;
import org.jactr.core.model.event.ModelEvent;
import org.jactr.core.model.event.ModelListenerAdaptor;
import org.jactr.core.runtime.ACTRRuntime;
import org.jactr.instrument.IInstrument;
/**
* locking mechanism, that when closed, will block all the models it is
* installed into.
*
* @author harrison
*/
public class ModelsLock extends ModelListenerAdaptor implements IInstrument
{
/**
* Logger definition
*/
static private final transient Log LOGGER = LogFactory
.getLog(ModelsLock.class);
private ReentrantLock _lock = new ReentrantLock();
private Condition _lockCondition = _lock
.newCondition();
private volatile boolean _shouldBlock = false;
private Set<IModel> _installed = new HashSet<IModel>();
/**
* who are we going to lock
*/
private Set<IModel> _modelsToLock = new HashSet<IModel>();
/**
* the models that are currently blocking
*/
private Set<IModel> _modelsLocked = new HashSet<IModel>();
private volatile CompletableFuture<Boolean> _currentRequest;
/*
* we actually block at the start of the next cycle. We do it at the start,
* because the normal cycle blocks on the clock after the cycle stops (since
* that is when we now the next timestep). It is also safe if the close()
* calls are received at different points in related models. (non-Javadoc)
* @see org.jactr.instrument.IInstrument#initialize()
*/
public void initialize()
{
}
@Override
public void modelStarted(ModelEvent me)
{
// add to the set
try
{
_lock.lock();
/*
* if we should enable locking, add it now. the model will block almost
* immediately after this for the first cycle.
*/
if (_installed.contains(me.getSource()))
_modelsToLock.add(me.getSource());
_lockCondition.signalAll();
}
finally
{
_lock.unlock();
}
}
@Override
public void modelStopped(ModelEvent me)
{
// remove from the set
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format(
"%s has stopped, removing from lock set @ %.2f", me.getSource(),
ACTRRuntime.getRuntime().getClock(me.getSource()).getTime()));
try
{
_lock.lock();
_modelsToLock.remove(me.getSource());
_modelsLocked.remove(me.getSource());
_lockCondition.signalAll();
}
finally
{
_lock.unlock();
}
}
@Override
public void cycleStarted(ModelEvent me)
{
// yup.
checkAndBlock(me.getSource());
}
public void install(IModel model)
{
try
{
_lock.lock();
_installed.add(model);
// inline
model.addListener(this, null);
_lockCondition.signalAll();
}
finally
{
_lock.unlock();
}
}
public void uninstall(IModel model)
{
try
{
_lock.lock();
_installed.remove(model);
model.removeListener(this);
_modelsToLock.remove(model);
_lockCondition.signalAll();
}
finally
{
_lock.unlock();
}
}
/**
* returns true if the gate is closed, but not necessarily if everyone is.
*
* @return
*/
public boolean isClosed()
{
try
{
_lock.lock();
return _shouldBlock;
}
finally
{
_lock.unlock();
}
}
public boolean areAllFree()
{
try
{
_lock.lock();
return _modelsLocked.size() == 0;
}
finally
{
_lock.unlock();
}
}
public boolean areAllBlocked()
{
try
{
_lock.lock();
return _modelsToLock.size() > 0
&& _modelsLocked.containsAll(_modelsToLock);
}
finally
{
_lock.unlock();
}
}
/**
* close the lock on all the installed models. To block until all the models,
* just call {@link Future#get()}. The return boolean will be true, unless
* there are no models. i.e., If no models are running, it will return false.
*
* @return
*/
public CompletableFuture<Boolean> close()
{
try
{
_lock.lock();
_shouldBlock = true;
_currentRequest = new CompletableFuture<Boolean>();
_lockCondition.signalAll();
}
finally
{
_lock.unlock();
}
return _currentRequest;
}
/**
* release the models.
*
* @return
*/
public CompletableFuture<Boolean> open()
{
try
{
_lock.lock();
_shouldBlock = false;
_currentRequest = new CompletableFuture<Boolean>();
_lockCondition.signalAll();
}
finally
{
_lock.unlock();
}
return _currentRequest;
}
private void allBocked()
{
if (_currentRequest != null) _currentRequest.complete(true);
_currentRequest = null;
}
private void allFreed()
{
if (_currentRequest != null) _currentRequest.complete(true);
_currentRequest = null;
}
private boolean shouldBlock()
{
return _shouldBlock;
}
/**
* do the blocking.
*
* @param model
*/
protected void checkAndBlock(IModel model)
{
long start = System.currentTimeMillis();
boolean blocked = false;
boolean allAreBlocked = false;
try
{
_lock.lock();
if (shouldBlock() && _modelsToLock.contains(model))
{
_modelsLocked.add(model);
blocked = true;
allAreBlocked = areAllBlocked();
}
}
finally
{
_lock.unlock();
}
if (allAreBlocked) allBocked();
if (blocked)
try
{
_lock.lock();
while (shouldBlock() && _modelsToLock.contains(model))
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("Blocking %s @ %.2f", model, ACTRRuntime
.getRuntime().getClock(model).getTime()));
_lockCondition.await(250, TimeUnit.MILLISECONDS);
}
if (LOGGER.isDebugEnabled())
{
long total = System.currentTimeMillis() - start;
LOGGER.debug(String.format("blocked %s for %dms", model, total));
}
}
catch (InterruptedException ie)
{
return;
}
finally
{
_modelsLocked.remove(model);
boolean allFree = areAllFree();
_lock.unlock();
if (allFree) allFreed();
}
// try
// {
// _lock.lock();
//
// long start = System.currentTimeMillis();
// boolean blocked = false;
//
// while (shouldBlock() && _modelsToLock.contains(model))
// {
// blocked = true;
// _modelsLocked.add(model);
// if (LOGGER.isDebugEnabled())
// LOGGER.debug(String.format("Blocking %s @ %.2f", model, ACTRRuntime
// .getRuntime().getClock(model).getTime()));
//
// if (areAllBlocked()) allBocked();
//
// _lockCondition.await();
// }
//
// if (LOGGER.isDebugEnabled() && blocked)
// {
// long total = System.currentTimeMillis() - start;
// LOGGER.debug(String.format("blocked %s for %dms", model, total));
// }
//
// }
// catch (InterruptedException ie)
// {
// return;
// }
// finally
// {
// _modelsLocked.remove(model);
//
// if (areAllFree()) allFreed();
//
// _lock.unlock();
// }
}
}