* SimpleTimer2: Synchronization improvements (ticket #653)

This commit is contained in:
zzz
2012-08-22 17:40:47 +00:00
parent 85fbbf8980
commit 7c7e131dc0

View File

@ -38,7 +38,7 @@ public class SimpleTimer2 {
private static final int MAX_THREADS = 4; private static final int MAX_THREADS = 4;
private final ScheduledThreadPoolExecutor _executor; private final ScheduledThreadPoolExecutor _executor;
private final String _name; private final String _name;
private int _count; private volatile int _count;
private final int _threads; private final int _threads;
/** /**
@ -64,7 +64,6 @@ public class SimpleTimer2 {
*/ */
protected SimpleTimer2(I2PAppContext context, String name, boolean prestartAllThreads) { protected SimpleTimer2(I2PAppContext context, String name, boolean prestartAllThreads) {
_name = name; _name = name;
_count = 0;
long maxMemory = Runtime.getRuntime().maxMemory(); long maxMemory = Runtime.getRuntime().maxMemory();
if (maxMemory == Long.MAX_VALUE) if (maxMemory == Long.MAX_VALUE)
maxMemory = 96*1024*1024l; maxMemory = 96*1024*1024l;
@ -127,6 +126,22 @@ public class SimpleTimer2 {
return _executor.schedule(t, timeoutMs, TimeUnit.MILLISECONDS); return _executor.schedule(t, timeoutMs, TimeUnit.MILLISECONDS);
} }
/**
* state of a given TimedEvent
*
* valid transitions:
* {IDLE,CANCELLED,RUNNING} -> SCHEDULED [ -> SCHEDULED ]* -> RUNNING -> {IDLE,CANCELLED,SCHEDULED}
* {IDLE,CANCELLED,RUNNING} -> SCHEDULED [ -> SCHEDULED ]* -> CANCELLED
*
* anything else is invalid.
*/
private enum TimedEventState {
IDLE,
SCHEDULED,
RUNNING,
CANCELLED
};
/** /**
* Similar to SimpleTimer.TimedEvent but users must extend instead of implement, * Similar to SimpleTimer.TimedEvent but users must extend instead of implement,
* and all schedule and cancel methods are through this class rather than SimpleTimer2. * and all schedule and cancel methods are through this class rather than SimpleTimer2.
@ -152,17 +167,25 @@ public class SimpleTimer2 {
*/ */
public static abstract class TimedEvent implements Runnable { public static abstract class TimedEvent implements Runnable {
private final Log _log; private final Log _log;
private SimpleTimer2 _pool; private final SimpleTimer2 _pool;
private int _fuzz; private int _fuzz;
protected static final int DEFAULT_FUZZ = 3; protected static final int DEFAULT_FUZZ = 3;
private ScheduledFuture _future; // _executor.remove() doesn't work so we have to use this private ScheduledFuture _future; // _executor.remove() doesn't work so we have to use this
// ... and I expect cancelling this way is more efficient // ... and I expect cancelling this way is more efficient
/** state of the current event. All access should be under lock. */
private TimedEventState _state;
/** absolute time this event should run next time. LOCKING: this */
private long _nextRun;
/** whether this was scheduled during RUNNING state. LOCKING: this */
private boolean _rescheduleAfterRun;
/** must call schedule() later */ /** must call schedule() later */
public TimedEvent(SimpleTimer2 pool) { public TimedEvent(SimpleTimer2 pool) {
_pool = pool; _pool = pool;
_fuzz = DEFAULT_FUZZ; _fuzz = DEFAULT_FUZZ;
_log = I2PAppContext.getGlobalContext().logManager().getLog(SimpleTimer2.class); _log = I2PAppContext.getGlobalContext().logManager().getLog(SimpleTimer2.class);
_state = TimedEventState.IDLE;
} }
/** automatically schedules, don't use this one if you have other things to do first */ /** automatically schedules, don't use this one if you have other things to do first */
@ -177,7 +200,7 @@ public class SimpleTimer2 {
* an inactivity timer. * an inactivity timer.
* Default 3 ms. * Default 3 ms.
*/ */
public void setFuzz(int fuzz) { public synchronized void setFuzz(int fuzz) {
_fuzz = fuzz; _fuzz = fuzz;
} }
@ -189,11 +212,26 @@ public class SimpleTimer2 {
*/ */
public synchronized void schedule(long timeoutMs) { public synchronized void schedule(long timeoutMs) {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Scheduling: " + this + " timeout = " + timeoutMs); _log.debug("Scheduling: " + this + " timeout = " + timeoutMs + " state: " + _state);
if (timeoutMs <= 0 && _log.shouldLog(Log.WARN)) if (timeoutMs <= 0 && _log.shouldLog(Log.WARN))
timeoutMs = 1; // otherwise we may execute before _future is updated, which is fine timeoutMs = 1; // otherwise we may execute before _future is updated, which is fine
// except it triggers 'early execution' warning logging // except it triggers 'early execution' warning logging
_future = _pool.schedule(this, timeoutMs);
// always set absolute time of execution
_nextRun = timeoutMs + System.currentTimeMillis();
switch(_state) {
case RUNNING:
_rescheduleAfterRun = true; // signal that we need rescheduling.
break;
case IDLE: // fall through
case CANCELLED:
_future = _pool.schedule(this, timeoutMs);
_state = TimedEventState.SCHEDULED;
break;
case SCHEDULED: // nothing
}
} }
/** /**
@ -215,19 +253,21 @@ public class SimpleTimer2 {
* two timeouts, else use the later * two timeouts, else use the later
*/ */
public synchronized void reschedule(long timeoutMs, boolean useEarliestTime) { public synchronized void reschedule(long timeoutMs, boolean useEarliestTime) {
final long now = System.currentTimeMillis();
long oldTimeout; long oldTimeout;
boolean scheduled = _future != null && !_future.isDone(); boolean scheduled = _state == TimedEventState.SCHEDULED;
if (scheduled) if (scheduled)
oldTimeout = _future.getDelay(TimeUnit.MILLISECONDS); oldTimeout = _nextRun - now;
else else
oldTimeout = timeoutMs; oldTimeout = timeoutMs;
// don't bother rescheduling if within _fuzz ms // don't bother rescheduling if within _fuzz ms
if ((oldTimeout - _fuzz > timeoutMs && useEarliestTime) || if ((oldTimeout - _fuzz > timeoutMs && useEarliestTime) ||
(oldTimeout + _fuzz < timeoutMs && !useEarliestTime)|| (oldTimeout + _fuzz < timeoutMs && !useEarliestTime)||
(!scheduled)) { (!scheduled)) {
if (scheduled) { if (scheduled && (now + timeoutMs) < _nextRun) {
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
_log.info("Re-scheduling: " + this + " timeout = " + timeoutMs + " old timeout was " + oldTimeout); _log.info("Re-scheduling: " + this + " timeout = " + timeoutMs + " old timeout was " + oldTimeout + " state: " + _state);
cancel(); cancel();
} }
schedule(timeoutMs); schedule(timeoutMs);
@ -245,9 +285,23 @@ public class SimpleTimer2 {
/** returns true if cancelled */ /** returns true if cancelled */
public synchronized boolean cancel() { public synchronized boolean cancel() {
if (_future == null) // always clear
return false; _rescheduleAfterRun = false;
return _future.cancel(false);
switch(_state) {
case CANCELLED: // fall through
case IDLE:
break; // my preference is to throw IllegalState here, but let it be.
case RUNNING: // fall through
case SCHEDULED:
boolean cancelled = _future.cancel(false);
if (cancelled)
_state = TimedEventState.CANCELLED;
else {} // log something as this could be serious, we remain RUNNING otherwise
return cancelled;
}
return false;
} }
public void run() { public void run() {
@ -255,6 +309,32 @@ public class SimpleTimer2 {
_log.debug("Running: " + this); _log.debug("Running: " + this);
long before = System.currentTimeMillis(); long before = System.currentTimeMillis();
long delay = 0; long delay = 0;
synchronized(this) {
if (_rescheduleAfterRun)
throw new IllegalStateException("rescheduleAfterRun cannot be true here");
switch(_state) {
case CANCELLED:
return; // goodbye
case IDLE: // fall through
case RUNNING:
throw new IllegalStateException("not possible to be in " + _state);
case SCHEDULED: // proceed, switch to IDLE in case I need to reschedule
_state = TimedEventState.IDLE;
}
// if I was rescheduled by the user, re-submit myself to the executor.
int difference = (int)(_nextRun - before); // careful with long uptimes
if (difference > _fuzz) {
schedule(difference);
return;
}
// else proceed to run
_state = TimedEventState.RUNNING;
}
// cancel()-ing after this point only works if the event supports it explicitly
// none of these _future checks should be necessary anymore
if (_future != null) if (_future != null)
delay = _future.getDelay(TimeUnit.MILLISECONDS); delay = _future.getDelay(TimeUnit.MILLISECONDS);
else if (_log.shouldLog(Log.WARN)) else if (_log.shouldLog(Log.WARN))
@ -268,6 +348,23 @@ public class SimpleTimer2 {
timeReached(); timeReached();
} catch (Throwable t) { } catch (Throwable t) {
_log.log(Log.CRIT, _pool + ": Timed task " + this + " exited unexpectedly, please report", t); _log.log(Log.CRIT, _pool + ": Timed task " + this + " exited unexpectedly, please report", t);
} finally { // must be in finally
synchronized(this) {
switch(_state) {
case SCHEDULED: // fall through
case IDLE:
throw new IllegalStateException("can't be " + _state);
case CANCELLED:
break; // nothing
case RUNNING:
_state = TimedEventState.IDLE;
// do we need to reschedule?
if (_rescheduleAfterRun) {
_rescheduleAfterRun = false;
schedule(_nextRun - System.currentTimeMillis());
}
}
}
} }
long time = System.currentTimeMillis() - before; long time = System.currentTimeMillis() - before;
if (time > 500 && _log.shouldLog(Log.WARN)) if (time > 500 && _log.shouldLog(Log.WARN))