From a5ab6f576d98d5199af4ff4e26ac568f51eca649 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Sat, 31 Jan 2009 14:22:07 +0000
Subject: [PATCH]     * SimpleScheduler: New replacement for SimpleTimer when
 events       will not be rescheduled or cancelled, to reduce SimpleTimer     
  lock contention

---
 .../src/org/klomp/snark/I2PSnarkUtil.java     |   3 +-
 .../org/klomp/snark/PeerConnectionOut.java    |   3 +-
 .../i2p/i2ptunnel/I2PTunnelClientBase.java    |   3 +-
 .../net/i2p/client/streaming/Connection.java  |   9 +-
 .../client/streaming/ConnectionHandler.java   |   3 +-
 .../streaming/ConnectionPacketHandler.java    |   3 +-
 .../src/net/i2p/apps/systray/SysTray.java     |   4 +-
 .../src/net/i2p/client/I2PSessionImpl.java    |   3 +-
 .../crypto/TransientSessionKeyManager.java    |   4 +-
 core/java/src/net/i2p/util/ByteCache.java     |   3 +-
 .../src/net/i2p/util/SimpleScheduler.java     | 164 ++++++++++++++++++
 router/java/src/net/i2p/router/Router.java    |   7 +-
 .../router/client/ClientConnectionRunner.java |   3 +-
 .../i2p/router/peermanager/PeerManager.java   |   5 +-
 .../transport/udp/EstablishmentManager.java   |   9 +-
 .../router/transport/udp/PeerTestManager.java |   9 +-
 .../i2p/router/transport/udp/UDPReceiver.java |   3 +-
 .../router/transport/udp/UDPTransport.java    |   3 +-
 18 files changed, 207 insertions(+), 34 deletions(-)
 create mode 100644 core/java/src/net/i2p/util/SimpleScheduler.java

diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
index f014580ec..26ed5860f 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
@@ -24,6 +24,7 @@ import net.i2p.data.Destination;
 import net.i2p.data.Hash;
 import net.i2p.util.EepGet;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -183,7 +184,7 @@ public class I2PSnarkUtil {
             synchronized (_shitlist) {
                 _shitlist.add(dest);
             }
-            SimpleTimer.getInstance().addEvent(new Unshitlist(dest), 10*60*1000);
+            SimpleScheduler.getInstance().addEvent(new Unshitlist(dest), 10*60*1000);
             throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
         }
     }
diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java
index 8fed9577a..1a53c342f 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java
@@ -28,6 +28,7 @@ import java.util.List;
 
 import net.i2p.util.I2PAppThread;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 class PeerConnectionOut implements Runnable
@@ -215,7 +216,7 @@ class PeerConnectionOut implements Runnable
   private void addMessage(Message m)
   {
     if (m.type == Message.PIECE)
-      SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
+      SimpleScheduler.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
     synchronized(sendQueue)
       {
         sendQueue.add(m);
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
index d6e5bf9f9..38311eaf1 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java
@@ -27,6 +27,7 @@ import net.i2p.data.Destination;
 import net.i2p.util.EventDispatcher;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable {
@@ -401,7 +402,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
         }
         
         if (_maxWaitTime > 0)
-            SimpleTimer.getInstance().addEvent(new CloseEvent(s), _maxWaitTime);
+            SimpleScheduler.getInstance().addEvent(new CloseEvent(s), _maxWaitTime);
 
         synchronized (_waitingSockets) {
             _waitingSockets.add(s);
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java
index 85872e9c5..ee93d20ee 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java
@@ -12,6 +12,7 @@ import net.i2p.client.I2PSession;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Destination;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -246,7 +247,7 @@ public class Connection {
     void sendReset() {
         if (_disconnectScheduledOn < 0) {
             _disconnectScheduledOn = _context.clock().now();
-            SimpleTimer.getInstance().addEvent(new DisconnectEvent(), DISCONNECT_TIMEOUT);
+            SimpleScheduler.getInstance().addEvent(new DisconnectEvent(), DISCONNECT_TIMEOUT);
         }
         long now = _context.clock().now();
         if (_resetSentOn + 10*1000 > now) return; // don't send resets too fast
@@ -460,7 +461,7 @@ public class Connection {
     void resetReceived() {
         if (_disconnectScheduledOn < 0) {
             _disconnectScheduledOn = _context.clock().now();
-            SimpleTimer.getInstance().addEvent(new DisconnectEvent(), DISCONNECT_TIMEOUT);
+            SimpleScheduler.getInstance().addEvent(new DisconnectEvent(), DISCONNECT_TIMEOUT);
         }
         _resetReceived = true;
         MessageOutputStream mos = _outputStream;
@@ -509,7 +510,7 @@ public class Connection {
         if (removeFromConMgr) {
             if (_disconnectScheduledOn < 0) {
                 _disconnectScheduledOn = _context.clock().now();
-                SimpleTimer.getInstance().addEvent(new DisconnectEvent(), DISCONNECT_TIMEOUT);
+                SimpleScheduler.getInstance().addEvent(new DisconnectEvent(), DISCONNECT_TIMEOUT);
             }
         }
         _connected = false;
@@ -708,7 +709,7 @@ public class Connection {
         _closeSentOn = when;
         if (_disconnectScheduledOn < 0) {
             _disconnectScheduledOn = _context.clock().now();
-            SimpleTimer.getInstance().addEvent(new DisconnectEvent(), DISCONNECT_TIMEOUT);
+            SimpleScheduler.getInstance().addEvent(new DisconnectEvent(), DISCONNECT_TIMEOUT);
         }
     }
     public long getCloseReceivedOn() { return _closeReceivedOn; }
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java
index 7d1d4827f..a123708e4 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java
@@ -5,6 +5,7 @@ import java.util.List;
 
 import net.i2p.I2PAppContext;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -54,7 +55,7 @@ class ConnectionHandler {
         }
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Receive new SYN: " + packet + ": timeout in " + _acceptTimeout);
-        RetransmissionTimer.getInstance().addEvent(new TimeoutSyn(packet), _acceptTimeout);
+        SimpleScheduler.getInstance().addEvent(new TimeoutSyn(packet), _acceptTimeout);
         synchronized (_synQueue) {
             _synQueue.add(packet);
             _synQueue.notifyAll();
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java
index 7c445f038..f7b245cb8 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionPacketHandler.java
@@ -7,6 +7,7 @@ import net.i2p.I2PException;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Destination;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -168,7 +169,7 @@ public class ConnectionPacketHandler {
                 // take note of congestion
                 if (_log.shouldLog(Log.WARN))
                     _log.warn("congestion.. dup " + packet);
-                RetransmissionTimer.getInstance().addEvent(new AckDup(con), con.getOptions().getSendAckDelay());
+                SimpleScheduler.getInstance().addEvent(new AckDup(con), con.getOptions().getSendAckDelay());
                 //con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
                 //fastAck = true;
             } else {
diff --git a/apps/systray/java/src/net/i2p/apps/systray/SysTray.java b/apps/systray/java/src/net/i2p/apps/systray/SysTray.java
index 380c5b172..4a635fd08 100644
--- a/apps/systray/java/src/net/i2p/apps/systray/SysTray.java
+++ b/apps/systray/java/src/net/i2p/apps/systray/SysTray.java
@@ -11,6 +11,7 @@ package net.i2p.apps.systray;
 
 import java.awt.Frame;
 
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 import snoozesoft.systray4j.SysTrayMenu;
 import snoozesoft.systray4j.SysTrayMenuEvent;
@@ -60,14 +61,13 @@ public class SysTray implements SysTrayMenuListener {
     private SysTray() {
         _sysTrayMenuIcon.addSysTrayMenuListener(this);
         createSysTrayMenu();
-        SimpleTimer.getInstance().addEvent(new RefreshDisplayEvent(), REFRESH_DISPLAY_FREQUENCY);
+        SimpleScheduler.getInstance().addPeriodicEvent(new RefreshDisplayEvent(), REFRESH_DISPLAY_FREQUENCY);
     }
     
     private static final long REFRESH_DISPLAY_FREQUENCY = 30*1000;
     private class RefreshDisplayEvent implements SimpleTimer.TimedEvent {
         public void timeReached() {
             refreshDisplay();
-            SimpleTimer.getInstance().addEvent(RefreshDisplayEvent.this, REFRESH_DISPLAY_FREQUENCY);
         }
     }
 
diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java
index a57957107..d4ff7360a 100644
--- a/core/java/src/net/i2p/client/I2PSessionImpl.java
+++ b/core/java/src/net/i2p/client/I2PSessionImpl.java
@@ -40,6 +40,7 @@ import net.i2p.data.i2cp.MessagePayloadMessage;
 import net.i2p.data.i2cp.SessionId;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -369,7 +370,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
             if (_log.shouldLog(Log.INFO))
                 _log.info(getPrefix() + "Notified availability for session " + _sessionId + ", message " + id);
         }
-        SimpleTimer.getInstance().addEvent(new VerifyUsage(mid), 30*1000);
+        SimpleScheduler.getInstance().addEvent(new VerifyUsage(mid), 30*1000);
     }
     private class VerifyUsage implements SimpleTimer.TimedEvent {
         private Long _msgId;
diff --git a/core/java/src/net/i2p/crypto/TransientSessionKeyManager.java b/core/java/src/net/i2p/crypto/TransientSessionKeyManager.java
index 1b160f8dd..0d71677a9 100644
--- a/core/java/src/net/i2p/crypto/TransientSessionKeyManager.java
+++ b/core/java/src/net/i2p/crypto/TransientSessionKeyManager.java
@@ -24,6 +24,7 @@ import net.i2p.data.PublicKey;
 import net.i2p.data.SessionKey;
 import net.i2p.data.SessionTag;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -70,7 +71,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
         _inboundTagSets = new HashMap(1024);
         context.statManager().createRateStat("crypto.sessionTagsExpired", "How many tags/sessions are expired?", "Encryption", new long[] { 10*60*1000, 60*60*1000, 3*60*60*1000 });
         context.statManager().createRateStat("crypto.sessionTagsRemaining", "How many tags/sessions are remaining after a cleanup?", "Encryption", new long[] { 10*60*1000, 60*60*1000, 3*60*60*1000 });
-        SimpleTimer.getInstance().addEvent(new CleanupEvent(), 60*1000);
+        SimpleScheduler.getInstance().addPeriodicEvent(new CleanupEvent(), 60*1000);
     }
     private TransientSessionKeyManager() { this(null); }
     
@@ -80,7 +81,6 @@ class TransientSessionKeyManager extends SessionKeyManager {
             int expired = aggressiveExpire();
             long expireTime = _context.clock().now() - beforeExpire;
             _context.statManager().addRateData("crypto.sessionTagsExpired", expired, expireTime);
-            SimpleTimer.getInstance().addEvent(CleanupEvent.this, 60*1000);
         }
     }
 
diff --git a/core/java/src/net/i2p/util/ByteCache.java b/core/java/src/net/i2p/util/ByteCache.java
index aadc721aa..4bd3da6ef 100644
--- a/core/java/src/net/i2p/util/ByteCache.java
+++ b/core/java/src/net/i2p/util/ByteCache.java
@@ -55,7 +55,7 @@ public final class ByteCache {
         _maxCached = maxCachedEntries;
         _entrySize = entrySize;
         _lastOverflow = -1;
-        SimpleTimer.getInstance().addEvent(new Cleanup(), CLEANUP_FREQUENCY);
+        SimpleScheduler.getInstance().addPeriodicEvent(new Cleanup(), CLEANUP_FREQUENCY);
         _log = I2PAppContext.getGlobalContext().logManager().getLog(ByteCache.class);
     }
     
@@ -120,7 +120,6 @@ public final class ByteCache {
                         _log.debug("Removing " + toRemove + " cached entries of size " + _entrySize);
                 }
             }
-            SimpleTimer.getInstance().addEvent(Cleanup.this, CLEANUP_FREQUENCY);
         }
     }
 }
diff --git a/core/java/src/net/i2p/util/SimpleScheduler.java b/core/java/src/net/i2p/util/SimpleScheduler.java
new file mode 100644
index 000000000..91415102c
--- /dev/null
+++ b/core/java/src/net/i2p/util/SimpleScheduler.java
@@ -0,0 +1,164 @@
+package net.i2p.util;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ThreadFactory;
+
+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).
+ *
+ * This is like SimpleScheduler but addEvent() for an existing event adds a second
+ * job. Events cannot be cancelled or rescheduled.
+ *
+ * For events that cannot or will not be cancelled or rescheduled -
+ * for example, a call such as:
+ *       SimpleTimer.getInstance().addEvent(new FooEvent(bar), timeoutMs);
+ * use SimpleScheduler instead to reduce lock contention in SimpleTimer...
+ *
+ * For periodic events, use addPeriodicEvent(). Unlike SimpleTimer,
+ * uncaught Exceptions will not prevent subsequent executions.
+ *
+ * @author zzz
+ */
+public class SimpleScheduler {
+    private static final SimpleScheduler _instance = new SimpleScheduler();
+    public static SimpleScheduler getInstance() { return _instance; }
+    private static final int THREADS = 4;
+    private I2PAppContext _context;
+    private Log _log;
+    private ScheduledThreadPoolExecutor _executor;
+    private String _name;
+    private int _count;
+
+    protected SimpleScheduler() { this("SimpleScheduler"); }
+    protected SimpleScheduler(String name) {
+        _context = I2PAppContext.getGlobalContext();
+        _log = _context.logManager().getLog(SimpleScheduler.class);
+        _name = name;
+        _count = 0;
+        _executor = new ScheduledThreadPoolExecutor(THREADS, new CustomThreadFactory());
+    }
+    
+    /**
+     * Removes the SimpleScheduler.
+     */
+    public void stop() {
+        _executor.shutdownNow();
+    }
+
+    /**
+     * Queue up the given event to be fired no sooner than timeoutMs from now.
+     *
+     * @param event
+     * @param timeoutMs 
+     */
+    public void addEvent(SimpleTimer.TimedEvent event, long timeoutMs) {
+        if (event == null)
+            throw new IllegalArgumentException("addEvent null");
+        RunnableEvent re = new RunnableEvent(event, timeoutMs);
+        re.schedule();
+    }
+    
+    public void addPeriodicEvent(SimpleTimer.TimedEvent event, long timeoutMs) {
+        addPeriodicEvent(event, timeoutMs, timeoutMs);
+    }
+    
+    /**
+     * Queue up the given event to be fired after initialDelay and every
+     * timeoutMs thereafter. The TimedEvent must not do its own rescheduling.
+     * As all Exceptions are caught in run(), these will not prevent
+     * subsequent executions (unlike SimpleTimer, where the TimedEvent does
+     * its own rescheduling)
+     *
+     * @param event
+     * @param initialDelay (ms)
+     * @param timeoutMs 
+     */
+    public void addPeriodicEvent(SimpleTimer.TimedEvent event, long initialDelay, long timeoutMs) {
+        if (event == null)
+            throw new IllegalArgumentException("addEvent null");
+        RunnableEvent re = new PeriodicRunnableEvent(event, initialDelay, timeoutMs);
+        re.schedule();
+    }
+    
+    private class CustomThreadFactory implements ThreadFactory {
+        public Thread newThread(Runnable r) {
+            Thread rv = Executors.defaultThreadFactory().newThread(r);
+            rv.setName(_name +  ' ' + (++_count) + '/' + THREADS);
+            rv.setDaemon(true);
+            return rv;
+        }
+    }
+
+    /**
+     * Same as SimpleTimer.TimedEvent but use run() instead of timeReached(), and remembers the time
+     */
+    private class RunnableEvent implements Runnable {
+        protected SimpleTimer.TimedEvent _timedEvent;
+        protected long _scheduled;
+
+        public RunnableEvent(SimpleTimer.TimedEvent t, long timeoutMs) {
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Creating w/ delay " + timeoutMs + " : " + t);
+            _timedEvent = t;
+            _scheduled = timeoutMs + System.currentTimeMillis();
+        }
+        public void schedule() {
+            _executor.schedule(this, _scheduled - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
+        }
+        public void run() {
+            if (_log.shouldLog(Log.DEBUG))
+                _log.debug("Running: " + _timedEvent);
+            long before = System.currentTimeMillis();
+            if (_log.shouldLog(Log.WARN) && before < _scheduled - 100)
+                _log.warn(_name + " wtf, early execution " + (_scheduled - before) + ": " + _timedEvent);
+            else if (_log.shouldLog(Log.WARN) && before > _scheduled + 1000)
+                _log.warn(" wtf, late execution " + (before - _scheduled) + ": " + _timedEvent + debug());
+            try {
+                _timedEvent.timeReached();
+            } catch (Throwable t) {
+                _log.log(Log.CRIT, _name + " wtf, event borked: " + _timedEvent, t);
+            }
+            long time = System.currentTimeMillis() - before;
+            if (time > 1000 && _log.shouldLog(Log.WARN))
+                _log.warn(_name + " wtf, event execution took " + time + ": " + _timedEvent);
+            long completed = _executor.getCompletedTaskCount();
+            if (_log.shouldLog(Log.INFO) && completed % 250  == 0)
+                _log.info(debug());
+        }
+    }
+
+    /** Run every timeoutMs. TimedEvent must not do its own reschedule via addEvent() */
+    private class PeriodicRunnableEvent extends RunnableEvent {
+        private long _timeoutMs;
+        private long _initialDelay;
+        public PeriodicRunnableEvent(SimpleTimer.TimedEvent t, long initialDelay, long timeoutMs) {
+            super(t, timeoutMs);
+            _initialDelay = initialDelay;
+            _timeoutMs = timeoutMs;
+            _scheduled = initialDelay + System.currentTimeMillis();
+        }
+        public void schedule() {
+            _executor.scheduleWithFixedDelay(this, _initialDelay, _timeoutMs, TimeUnit.MILLISECONDS);
+        }
+        public void run() {
+            super.run();
+            _scheduled = _timeoutMs + System.currentTimeMillis();
+        }
+    }
+
+    private String debug() {
+        return
+            " Pool: " + _name +
+            " Active: " + _executor.getActiveCount() + '/' + _executor.getPoolSize() +
+            " Completed: " + _executor.getCompletedTaskCount() +
+            " Queued: " + _executor.getQueue().size();
+    }
+}
+
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index f7342413a..033678924 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -43,6 +43,7 @@ import net.i2p.stat.StatManager;
 import net.i2p.util.FileUtil;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -257,7 +258,7 @@ public class Router {
         _context.inNetMessagePool().startup();
         startupQueue();
         //_context.jobQueue().addJob(new CoalesceStatsJob(_context));
-        SimpleTimer.getInstance().addEvent(new CoalesceStatsEvent(_context), 0);
+        SimpleScheduler.getInstance().addPeriodicEvent(new CoalesceStatsEvent(_context), 20*1000);
         _context.jobQueue().addJob(new UpdateRoutingKeyModifierJob(_context));
         warmupCrypto();
         _sessionKeyPersistenceHelper.startup();
@@ -346,7 +347,7 @@ public class Router {
             if (blockingRebuild)
                 r.timeReached();
             else
-                SimpleTimer.getInstance().addEvent(r, 0);
+                SimpleScheduler.getInstance().addEvent(r, 0);
         } catch (DataFormatException dfe) {
             _log.log(Log.CRIT, "Internal error - unable to sign our own address?!", dfe);
         }
@@ -1261,8 +1262,6 @@ class CoalesceStatsEvent implements SimpleTimer.TimedEvent {
                 getContext().statManager().addRateData("bw.sendBps", (long)KBps, 60*1000);
             }
         }
-        
-        SimpleTimer.getInstance().addEvent(this, 20*1000);
     }
 }
 
diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
index 133ad142c..189568ead 100644
--- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
+++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java
@@ -38,6 +38,7 @@ import net.i2p.router.RouterContext;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
 import net.i2p.util.RandomSource;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -419,7 +420,7 @@ public class ClientConnectionRunner {
                     // theirs is newer
                 } else {
                     // ours is newer, so wait a few secs and retry
-                    SimpleTimer.getInstance().addEvent(new Rerequest(set, expirationTime, onCreateJob, onFailedJob), 3*1000);
+                    SimpleScheduler.getInstance().addEvent(new Rerequest(set, expirationTime, onCreateJob, onFailedJob), 3*1000);
                 }
                 // fire onCreated?
                 return; // already requesting
diff --git a/router/java/src/net/i2p/router/peermanager/PeerManager.java b/router/java/src/net/i2p/router/peermanager/PeerManager.java
index b2b16a00d..1c265ee67 100644
--- a/router/java/src/net/i2p/router/peermanager/PeerManager.java
+++ b/router/java/src/net/i2p/router/peermanager/PeerManager.java
@@ -24,6 +24,7 @@ import net.i2p.router.PeerSelectionCriteria;
 import net.i2p.router.RouterContext;
 import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -50,7 +51,7 @@ class PeerManager {
             _peersByCapability[i] = new ArrayList(64);
         loadProfiles();
         ////_context.jobQueue().addJob(new EvaluateProfilesJob(_context));
-        SimpleTimer.getInstance().addEvent(new Reorg(), 0);
+        SimpleScheduler.getInstance().addPeriodicEvent(new Reorg(), 0, 30*1000);
         //_context.jobQueue().addJob(new PersistProfilesJob(_context, this));
     }
     
@@ -60,8 +61,6 @@ class PeerManager {
                 _organizer.reorganize(true);
             } catch (Throwable t) {
                 _log.log(Log.CRIT, "Error evaluating profiles", t);
-            } finally {
-                SimpleTimer.getInstance().addEvent(Reorg.this, 30*1000);
             }
         }
     }
diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
index 6ab159408..896fe1ce4 100644
--- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
+++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java
@@ -22,6 +22,7 @@ import net.i2p.router.Router;
 import net.i2p.router.RouterContext;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -184,7 +185,7 @@ public class EstablishmentManager {
                                                        msg.getTarget().getIdentity(), 
                                                        new SessionKey(addr.getIntroKey()), addr);
                     _outboundStates.put(to, state);
-                    SimpleTimer.getInstance().addEvent(new Expire(to, state), 10*1000);
+                    SimpleScheduler.getInstance().addEvent(new Expire(to, state), 10*1000);
                 }
             }
             if (state != null) {
@@ -394,7 +395,7 @@ public class EstablishmentManager {
                                                msg.getTarget().getIdentity(), 
                                                new SessionKey(addr.getIntroKey()), addr);
             _outboundStates.put(to, qstate);
-            SimpleTimer.getInstance().addEvent(new Expire(to, qstate), 10*1000);
+            SimpleScheduler.getInstance().addEvent(new Expire(to, qstate), 10*1000);
 
             for (int i = 0; i < queued.size(); i++) {
                 OutNetMessage m = (OutNetMessage)queued.get(i);
@@ -477,7 +478,7 @@ public class EstablishmentManager {
         dsm.setMessageExpiration(_context.clock().now()+10*1000);
         dsm.setMessageId(_context.random().nextLong(I2NPMessage.MAX_ID_VALUE));
         _transport.send(dsm, peer);
-        SimpleTimer.getInstance().addEvent(new PublishToNewInbound(peer), 0);
+        SimpleScheduler.getInstance().addEvent(new PublishToNewInbound(peer), 0);
     }
     private class PublishToNewInbound implements SimpleTimer.TimedEvent {
         private PeerState _peer;
@@ -629,7 +630,7 @@ public class EstablishmentManager {
                 }
             }
         }
-        SimpleTimer.getInstance().addEvent(new FailIntroduction(state, nonce), INTRO_ATTEMPT_TIMEOUT);
+        SimpleScheduler.getInstance().addEvent(new FailIntroduction(state, nonce), INTRO_ATTEMPT_TIMEOUT);
         state.setIntroNonce(nonce);
         _context.statManager().addRateData("udp.sendIntroRelayRequest", 1, 0);
         UDPPacket requests[] = _builder.buildRelayRequest(_transport, state, _transport.getIntroKey());
diff --git a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java
index 7aa3c2fa1..35c5511be 100644
--- a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java
+++ b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java
@@ -15,6 +15,7 @@ import net.i2p.data.SessionKey;
 import net.i2p.router.CommSystemFacade;
 import net.i2p.router.RouterContext;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -79,7 +80,7 @@ class PeerTestManager {
         
         sendTestToBob();
         
-        SimpleTimer.getInstance().addEvent(new ContinueTest(), RESEND_TIMEOUT);
+        SimpleScheduler.getInstance().addEvent(new ContinueTest(), RESEND_TIMEOUT);
     }
     
     private class ContinueTest implements SimpleTimer.TimedEvent {
@@ -103,7 +104,7 @@ class PeerTestManager {
                     // second message from Charlie yet
                     sendTestToCharlie();
                 }
-                SimpleTimer.getInstance().addEvent(ContinueTest.this, RESEND_TIMEOUT);
+                SimpleScheduler.getInstance().addEvent(ContinueTest.this, RESEND_TIMEOUT);
             }
         }
     }
@@ -430,7 +431,7 @@ class PeerTestManager {
                 synchronized (_activeTests) {
                     _activeTests.put(new Long(nonce), state);
                 }
-                SimpleTimer.getInstance().addEvent(new RemoveTest(nonce), MAX_CHARLIE_LIFETIME);
+                SimpleScheduler.getInstance().addEvent(new RemoveTest(nonce), MAX_CHARLIE_LIFETIME);
             }
 
             UDPPacket packet = _packetBuilder.buildPeerTestToBob(bobIP, from.getPort(), aliceIP, alicePort, aliceIntroKey, nonce, state.getBobCipherKey(), state.getBobMACKey());
@@ -511,7 +512,7 @@ class PeerTestManager {
                 synchronized (_activeTests) {
                     _activeTests.put(new Long(nonce), state);
                 }
-                SimpleTimer.getInstance().addEvent(new RemoveTest(nonce), MAX_CHARLIE_LIFETIME);
+                SimpleScheduler.getInstance().addEvent(new RemoveTest(nonce), MAX_CHARLIE_LIFETIME);
             }
             
             UDPPacket packet = _packetBuilder.buildPeerTestToCharlie(aliceIP, from.getPort(), aliceIntroKey, nonce, 
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java
index 10876a0e7..3535484c9 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java
@@ -9,6 +9,7 @@ import net.i2p.router.RouterContext;
 import net.i2p.router.transport.FIFOBandwidthLimiter;
 import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -115,7 +116,7 @@ public class UDPReceiver {
             long delay = ARTIFICIAL_DELAY_BASE + _context.random().nextInt(ARTIFICIAL_DELAY);
             if (_log.shouldLog(Log.INFO))
                 _log.info("Delay packet " + packet + " for " + delay);
-            SimpleTimer.getInstance().addEvent(new ArtificiallyDelayedReceive(packet), delay);
+            SimpleScheduler.getInstance().addEvent(new ArtificiallyDelayedReceive(packet), delay);
             return -1;
         }
         
diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
index 03714d7ef..e5185defa 100644
--- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
+++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java
@@ -33,6 +33,7 @@ import net.i2p.router.transport.Transport;
 import net.i2p.router.transport.TransportBid;
 import net.i2p.router.transport.TransportImpl;
 import net.i2p.util.Log;
+import net.i2p.util.SimpleScheduler;
 import net.i2p.util.SimpleTimer;
 
 /**
@@ -631,7 +632,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority
                     }
                     if (added) {
                         _context.statManager().addRateData("udp.dropPeerDroplist", droplistSize, 0);
-                        SimpleTimer.getInstance().addEvent(new RemoveDropList(remote), DROPLIST_PERIOD);
+                        SimpleScheduler.getInstance().addEvent(new RemoveDropList(remote), DROPLIST_PERIOD);
                     }
                 }
                 markUnreachable(peerHash);
-- 
GitLab