package org.jactr.tools.deadlock;
/*
* default logging
*/
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jactr.core.concurrent.ExecutorServices;
import org.jactr.core.model.IModel;
import org.jactr.core.model.event.IModelListener;
import org.jactr.core.model.event.ModelEvent;
import org.jactr.core.model.event.ModelListenerAdaptor;
import org.jactr.core.runtime.ACTRRuntime;
import org.jactr.core.runtime.controller.IController;
import org.jactr.instrument.IInstrument;
public class DeadLockDetector implements IInstrument
{
/**
* Logger definition
*/
static private final transient Log LOGGER = LogFactory
.getLog(DeadLockDetector.class);
final private Runnable _detector;
final private IModelListener _modelListener;
final private IDeadLockListener _listener;
final private long _inactivityTime;
final private long _delay;
// number of missed checks
private int _counter;
private double _lastTime = -1;
private double _currentTime = 0;
private long _lastTimeChange;
private boolean _scheduled = false;
/**
* zero arg for installing straight into models
*/
public DeadLockDetector()
{
this(null, 100000);
}
public DeadLockDetector(IDeadLockListener listener, long checkIntervalMS)
{
_listener = listener;
_inactivityTime = checkIntervalMS;
_delay = _inactivityTime / 10;
/**
* run periodically to test to see if the model time has changed
*/
_detector = new Runnable() {
public void run()
{
synchronized (DeadLockDetector.this)
{
_scheduled = false;
}
check();
}
};
/**
* flags the model time as having changed
*/
_modelListener = new ModelListenerAdaptor() {
@Override
public void cycleStarted(ModelEvent me)
{
touch(me); // accidental pun
}
};
}
public void initialize()
{
}
public void install(IModel model)
{
model.addListener(_modelListener, ExecutorServices.INLINE_EXECUTOR);
schedule();
}
public void uninstall(IModel model)
{
model.removeListener(_modelListener);
}
private void check()
{
IController controller = ACTRRuntime.getRuntime().getController();
if (controller == null || !controller.isRunning()
|| controller.isSuspended())
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("runtime not started or suspended, ignoring");
if (controller != null && controller.isSuspended()) synchronized (this)
{
// if suspended, we need to reset this, just in case
_counter = 0;
}
schedule();
return;
}
boolean deadlocked = false;
synchronized (this)
{
if (_lastTime == _currentTime)
{
_counter++;
if (LOGGER.isDebugEnabled())
LOGGER.debug("Time hasn't changed " + _counter + " times");
long delta = System.currentTimeMillis() - _lastTimeChange;
if (_counter >= 10 && delta >= _inactivityTime)
{
LOGGER.error("Deadlock has been detected after " + delta + "ms");
deadlocked = true;
}
else if (LOGGER.isDebugEnabled())
LOGGER.debug("only " + delta
+ "ms have elapsed, still some time left");
}
else if (LOGGER.isDebugEnabled())
LOGGER.debug("Time has changed [" + _currentTime + "/" + _lastTime
+ "]. All is good in the universe.");
_lastTime = _currentTime;
}
if (deadlocked)
{
DeadLockUtilities.dumpThreads("deadlock-threads.txt");
DeadLockUtilities.dumpHeap("deadlock-heap.hprof", true);
if (_listener != null) _listener.deadlockDetected();
}
else
schedule();
}
private void touch(ModelEvent event)
{
synchronized (this)
{
if (_currentTime == event.getSimulationTime()) return;
_counter = 0;
_currentTime = event.getSimulationTime();
_lastTimeChange = event.getSystemTime();
}
}
private void schedule()
{
try
{
if (_scheduled) return;
((ScheduledExecutorService) ExecutorServices
.getExecutor(ExecutorServices.PERIODIC)).schedule(_detector, _delay,
TimeUnit.MILLISECONDS);
_scheduled = true;
}
catch (RejectedExecutionException ree)
{
// ignore
_scheduled = false;
}
}
}