package net.i2p.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import net.i2p.I2PAppContext;
/**
* Simple event scheduler - toss an event on the queue and it gets fired at the
* appropriate time. The method that is fired however should NOT block (otherwise
* they b0rk the timer).
*
* WARNING - Deprecated.
* This is an inefficient mess. Use SimpleTimer2 if possible.
*/
public class SimpleTimer {
/**
* If you have a context, use context.simpleTimer() instead
* @deprecated use SimpleTimer2
*/
@Deprecated
public static SimpleTimer getInstance() {
return I2PAppContext.getGlobalContext().simpleTimer();
}
private final Log _log;
/** event time (Long) to event (TimedEvent) mapping */
private final TreeMap<Long, TimedEvent> _events;
/** event (TimedEvent) to event time (Long) mapping */
private final Map<TimedEvent, Long> _eventTimes;
private final List<TimedEvent> _readyEvents;
private SimpleStore runn;
private static final int MIN_THREADS = 2;
private static final int MAX_THREADS = 4;
/**
* To be instantiated by the context.
* Others should use context.simpleTimer() instead
* @deprecated use SimpleTimer2
*/
@Deprecated
public SimpleTimer(I2PAppContext context) {
this(context, "SimpleTimer");
}
/**
* To be instantiated by the context.
* Others should use context.simpleTimer() instead
* @deprecated use SimpleTimer2
*/
@Deprecated
private SimpleTimer(I2PAppContext context, String name) {
runn = new SimpleStore(true);
_log = context.logManager().getLog(SimpleTimer.class);
_events = new TreeMap<Long, TimedEvent>();
_eventTimes = new HashMap<TimedEvent, Long>(256);
_readyEvents = new ArrayList<TimedEvent>(4);
I2PThread runner = new I2PThread(new SimpleTimerRunner());
runner.setName(name);
runner.setDaemon(true);
runner.start();
long maxMemory = SystemVersion.getMaxMemory();
int threads = (int) Math.max(MIN_THREADS, Math.min(MAX_THREADS, 1 + (maxMemory / (32*1024*1024))));
for (int i = 1; i <= threads ; i++) {
I2PThread executor = new I2PThread(new Executor(context, _log, _readyEvents, runn));
executor.setName(name + "Executor " + i + '/' + threads);
executor.setDaemon(true);
executor.start();
}
context.addShutdownTask(new Shutdown());
}
/**
* @since 0.8.8
*/
private class Shutdown implements Runnable {
public void run() {
removeSimpleTimer();
}
}
/**
* Removes the SimpleTimer.
*/
public void removeSimpleTimer() {
synchronized(_events) {
runn.setAnswer(false);
_events.clear();
_eventTimes.clear();
_events.notifyAll();
}
synchronized (_readyEvents) {
_readyEvents.clear();
_readyEvents.notifyAll();
}
}
/**
*
* @param event
* @param timeoutMs
*/
public void reschedule(TimedEvent event, long timeoutMs) {
addEvent(event, timeoutMs, false);
}
/**
* Queue up the given event to be fired no sooner than timeoutMs from now.
* However, if this event is already scheduled, the event will be scheduled
* for the earlier of the two timeouts, which may be before this stated
* timeout. If this is not the desired behavior, call removeEvent first.
*
* @param event
* @param timeoutMs
*/
public void addEvent(TimedEvent event, long timeoutMs) { addEvent(event, timeoutMs, true); }
/**
* @param event
* @param timeoutMs
* @param useEarliestTime if its already scheduled, use the earlier of the
* two timeouts, else use the later
*/
public void addEvent(TimedEvent event, long timeoutMs, boolean useEarliestTime) {
int totalEvents = 0;
long now = System.currentTimeMillis();
long eventTime = now + timeoutMs;
Long time = Long.valueOf(eventTime);
synchronized (_events) {
// remove the old scheduled position, then reinsert it
Long oldTime = _eventTimes.get(event);
if (oldTime != null) {
if (useEarliestTime) {
if (oldTime.longValue() < eventTime) {
_events.notifyAll();
return; // already scheduled for sooner than requested
} else {
_events.remove(oldTime);
}
} else {
if (oldTime.longValue() > eventTime) {
_events.notifyAll();
return; // already scheduled for later than the given period
} else {
_events.remove(oldTime);
}
}
}
// FIXME if you plan to use this class again
while (_events.containsKey(time))
time = Long.valueOf(time.longValue() + 1);
_events.put(time, event);
_eventTimes.put(event, time);
if ( (_events.size() != _eventTimes.size()) ) {
_log.error("Skewed events: " + _events.size() + " for " + _eventTimes.size());
for (TimedEvent evt : _eventTimes.keySet()) {
Long when = _eventTimes.get(evt);
TimedEvent cur = _events.get(when);
if (cur != evt) {
_log.error("event " + evt + " @ " + when + ": " + cur);
}
}
}
totalEvents = _events.size();
_events.notifyAll();
}
if (time.longValue() > eventTime + 100) {
if (_log.shouldLog(Log.WARN))
_log.warn("Lots of timer congestion, had to push " + event + " back "
+ (time.longValue()-eventTime) + "ms (# events: " + totalEvents + ")");
}
long timeToAdd = System.currentTimeMillis() - now;
if (timeToAdd > 50) {
if (_log.shouldLog(Log.WARN))
_log.warn("timer contention: took " + timeToAdd + "ms to add a job with " + totalEvents + " queued");
}
}
public boolean removeEvent(TimedEvent evt) {
if (evt == null) return false;
synchronized (_events) {
Long when = _eventTimes.remove(evt);
if (when != null)
_events.remove(when);
return null != when;
}
}
/**
* Simple interface for events to be queued up and notified on expiration
*/
public interface TimedEvent {
/**
* the time requested has been reached (this call should NOT block,
* otherwise the whole SimpleTimer gets backed up)
*
*/
public void timeReached();
}
private long _occurredTime;
private long _occurredEventCount;
// not used
// private TimedEvent _recentEvents[] = new TimedEvent[5];
private class SimpleTimerRunner implements Runnable {
public void run() {
List<TimedEvent> eventsToFire = new ArrayList<TimedEvent>(1);
while(runn.getAnswer()) {
try {
synchronized (_events) {
//if (_events.size() <= 0)
// _events.wait();
//if (_events.size() > 100)
// _log.warn("> 100 events! " + _events.values());
long now = System.currentTimeMillis();
long nextEventDelay = -1;
Object nextEvent = null;
while(runn.getAnswer()) {
if(_events.isEmpty()) {
break;
}
Long when = _events.firstKey();
if (when.longValue() <= now) {
TimedEvent evt = _events.remove(when);
if (evt != null) {
_eventTimes.remove(evt);
eventsToFire.add(evt);
}
} else {
nextEventDelay = when.longValue() - now;
nextEvent = _events.get(when);
break;
}
}
if (eventsToFire.isEmpty()) {
if (nextEventDelay != -1) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Next event in " + nextEventDelay + ": " + nextEvent);
_events.wait(nextEventDelay);
} else {
_events.wait();
}
}
}
} catch (ThreadDeath td) {
return; // die
} catch (InterruptedException ie) {
// ignore
} catch (Throwable t) {
if (_log != null) {
_log.log(Log.CRIT, "Uncaught exception in the SimpleTimer!", t);
} else {
System.err.println("Uncaught exception in SimpleTimer");
t.printStackTrace();
}
}
long now = System.currentTimeMillis();
now = now - (now % 1000);
synchronized (_readyEvents) {
for (int i = 0; i < eventsToFire.size(); i++)
_readyEvents.add(eventsToFire.get(i));
_readyEvents.notifyAll();
}
if (_occurredTime == now) {
_occurredEventCount += eventsToFire.size();
} else {
_occurredTime = now;
if (_occurredEventCount > 2500) {
StringBuilder buf = new StringBuilder(128);
buf.append("Too many simpleTimerJobs (").append(_occurredEventCount);
buf.append(") in a second!");
_log.log(Log.WARN, buf.toString());
}
_occurredEventCount = 0;
}
eventsToFire.clear();
}
}
}
}