Modified the serial executor to support timing out of hung executor tasks.
git-svn-id: https://samskivert.googlecode.com/svn/trunk@1749 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -8,9 +8,11 @@ import java.util.ArrayList;
|
|||||||
import com.samskivert.Log;
|
import com.samskivert.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple serial executor, similar to what can be done in java 1.5, only
|
* Executes tasks serially, but each one on a separate thread. If a task times
|
||||||
* using 1.4 and samskivert-style tasks. This is like RunQueue only slightly
|
* out, the executor will attempt to interrupt the thread and abort the task,
|
||||||
* more sophisticated. Bloat!
|
* but will abandon the thread in any case after the abort attempt so that
|
||||||
|
* subsequent tasks can be processed. The threads created are daemon threads so
|
||||||
|
* that they will not block the eventual termination of the virtual machine.
|
||||||
*/
|
*/
|
||||||
public class SerialExecutor
|
public class SerialExecutor
|
||||||
{
|
{
|
||||||
@@ -28,9 +30,17 @@ public class SerialExecutor
|
|||||||
*/
|
*/
|
||||||
public boolean merge (ExecutorTask other);
|
public boolean merge (ExecutorTask other);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of milliseconds after which this task should be
|
||||||
|
* considered a lost cause. If the task times out, {@link #timedOut}
|
||||||
|
* will be called instead of {@link #resultReceived}.
|
||||||
|
*/
|
||||||
|
public long getTimeout ();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The portion of the task that will be executed on the executor's
|
* The portion of the task that will be executed on the executor's
|
||||||
* thread.
|
* thread. This method should handle {@link InterruptedException} as
|
||||||
|
* meaning that the task should be aborted.
|
||||||
*/
|
*/
|
||||||
public void executeTask ();
|
public void executeTask ();
|
||||||
|
|
||||||
@@ -40,6 +50,12 @@ public class SerialExecutor
|
|||||||
* task.
|
* task.
|
||||||
*/
|
*/
|
||||||
public void resultReceived ();
|
public void resultReceived ();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called instead of {@link #resultReceived} if the task
|
||||||
|
* does not complete within the requisite time.
|
||||||
|
*/
|
||||||
|
public void timedOut ();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,9 +104,19 @@ public class SerialExecutor
|
|||||||
{
|
{
|
||||||
_executingNow = !_queue.isEmpty();
|
_executingNow = !_queue.isEmpty();
|
||||||
if (_executingNow) {
|
if (_executingNow) {
|
||||||
|
// start up a thread to execute the task in question
|
||||||
ExecutorTask task = (ExecutorTask) _queue.remove(0);
|
ExecutorTask task = (ExecutorTask) _queue.remove(0);
|
||||||
ExecutorThread thread = new ExecutorThread(task);
|
final ExecutorThread thread = new ExecutorThread(task);
|
||||||
thread.start();
|
thread.start();
|
||||||
|
|
||||||
|
// start up a timer that will abort this thread after the specified
|
||||||
|
// timeout
|
||||||
|
new Interval() {
|
||||||
|
public void expired () {
|
||||||
|
// this will NOOP if the task has already completed
|
||||||
|
thread.abort();
|
||||||
|
}
|
||||||
|
}.schedule(task.getTimeout());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,22 +131,54 @@ public class SerialExecutor
|
|||||||
_task = task;
|
_task = task;
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited
|
public synchronized void abort ()
|
||||||
|
{
|
||||||
|
if (_task != null) {
|
||||||
|
final ExecutorTask task = _task;
|
||||||
|
// clear out the task reference which will let the running
|
||||||
|
// thread know to stop if/when executeTask() returns
|
||||||
|
_task = null;
|
||||||
|
|
||||||
|
// let the task know that it timed out
|
||||||
|
_receiver.postRunnable(new Runnable() {
|
||||||
|
public void run () {
|
||||||
|
try {
|
||||||
|
task.timedOut();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.logStackTrace(t);
|
||||||
|
}
|
||||||
|
checkNext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// finally interrupt the thread in hopes of waking it up from
|
||||||
|
// it's hangitude
|
||||||
|
interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void run ()
|
public void run ()
|
||||||
{
|
{
|
||||||
|
final ExecutorTask task = _task;
|
||||||
try {
|
try {
|
||||||
_task.executeTask();
|
_task.executeTask();
|
||||||
|
synchronized (this) {
|
||||||
|
if (_task == null) {
|
||||||
|
// we were aborted, abandon ship
|
||||||
|
System.err.println("Aborted!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_task = null;
|
||||||
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Log.warning("ExecutorTask choked: " + t);
|
|
||||||
Log.logStackTrace(t);
|
Log.logStackTrace(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
_receiver.postRunnable(new Runnable() {
|
_receiver.postRunnable(new Runnable() {
|
||||||
public void run () {
|
public void run () {
|
||||||
try {
|
try {
|
||||||
_task.resultReceived();
|
task.resultReceived();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Log.warning("ExecutorTask choked: " + t);
|
|
||||||
Log.logStackTrace(t);
|
Log.logStackTrace(t);
|
||||||
}
|
}
|
||||||
checkNext();
|
checkNext();
|
||||||
@@ -128,7 +186,6 @@ public class SerialExecutor
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The task to execute. */
|
|
||||||
protected ExecutorTask _task;
|
protected ExecutorTask _task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
package com.samskivert.util;
|
||||||
|
|
||||||
|
import junit.framework.Test;
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the {@link SerialExecutor} class.
|
||||||
|
*/
|
||||||
|
public class SerialExecutorTest extends TestCase
|
||||||
|
implements RunQueue
|
||||||
|
{
|
||||||
|
public SerialExecutorTest ()
|
||||||
|
{
|
||||||
|
super(SerialExecutorTest.class.getName());
|
||||||
|
_main = Thread.currentThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runTest ()
|
||||||
|
{
|
||||||
|
SerialExecutor executor = new SerialExecutor(this);
|
||||||
|
int added = 0;
|
||||||
|
|
||||||
|
// _sleeps++, _exits++, _results++
|
||||||
|
executor.addTask(new Sleeper(500L, false));
|
||||||
|
added++;
|
||||||
|
|
||||||
|
// _interrupts++, _exits++, _timeouts++
|
||||||
|
executor.addTask(new Sleeper(1500L, false));
|
||||||
|
added++;
|
||||||
|
|
||||||
|
// _interrupts++, _timeouts++
|
||||||
|
executor.addTask(new Sleeper(1500L, true));
|
||||||
|
added++;
|
||||||
|
|
||||||
|
// _sleeps++, _exits++, _results++
|
||||||
|
executor.addTask(new Sleeper(500L, false));
|
||||||
|
added++;
|
||||||
|
|
||||||
|
// process the results posted on our run queue
|
||||||
|
for (int ii = 0; ii < added; ii++) {
|
||||||
|
Runnable r = (Runnable)_queue.get();
|
||||||
|
r.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// give the executor threads a second to run to completion
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000L);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we got the expected number of sleeps, interrupts, etc.
|
||||||
|
assertCount("_sleeps", _sleeps, 2);
|
||||||
|
assertCount("_interrupts", _interrupts, 2);
|
||||||
|
assertCount("_exits", _exits, 3);
|
||||||
|
assertCount("_results", _results, 2);
|
||||||
|
assertCount("_timeouts", _timeouts, 2);
|
||||||
|
assertCount("_doubleints", _doubleints, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertCount (String field, int value, int expected)
|
||||||
|
{
|
||||||
|
assertTrue(field + " != " + expected + " (" + value + ")",
|
||||||
|
value == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void postRunnable (Runnable r)
|
||||||
|
{
|
||||||
|
_queue.append(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDispatchThread ()
|
||||||
|
{
|
||||||
|
return Thread.currentThread() == _main;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Test suite ()
|
||||||
|
{
|
||||||
|
return new SerialExecutorTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main (String[] args)
|
||||||
|
{
|
||||||
|
SerialExecutorTest test = new SerialExecutorTest();
|
||||||
|
test.runTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class Sleeper implements SerialExecutor.ExecutorTask
|
||||||
|
{
|
||||||
|
public Sleeper (long sleepFor, boolean hang) {
|
||||||
|
_sleepFor = sleepFor;
|
||||||
|
_hang = hang;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean merge (SerialExecutor.ExecutorTask task) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeout () {
|
||||||
|
return 1000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeTask () {
|
||||||
|
try {
|
||||||
|
Thread.sleep(_sleepFor);
|
||||||
|
_sleeps++;
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
_interrupts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_hang) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(100000L);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
_doubleints++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_exits++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resultReceived () {
|
||||||
|
_results++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void timedOut () {
|
||||||
|
_timeouts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long _sleepFor;
|
||||||
|
protected boolean _hang;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Thread _main;
|
||||||
|
protected Queue _queue = new Queue();
|
||||||
|
|
||||||
|
protected int _sleeps, _interrupts, _doubleints, _exits;
|
||||||
|
protected int _results, _timeouts;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user