diff --git a/history.txt b/history.txt
index 0d8517cb3a59db7816848b2f5db9a13754251592..6812caecc52b27711ddee5415d0a6e5d73c59409 100644
--- a/history.txt
+++ b/history.txt
@@ -1,3 +1,6 @@
+2020-12-16 zzz
+ * SSU: Fix occasional high CPU usage
+
 2020-12-11 zzz
  * Router (proposal 156):
    - Change router ECIES SKM to use N pattern
@@ -8,10 +11,10 @@
 2020-12-06 zzz
  * Console, webapps: Move web resources to wars
  * i2psnark:
-   - Add support for web seeds
+   - Add support for web seeds (ticket #2780)
    - Preserve file attribute strings in metainfo
  * Streaming: Add Retry-After header to throttle response
- * Util: Change DoH to RFC 8484 protocol
+ * Util: Change DoH to RFC 8484 protocol (ticket #2201)
 
 * 2020-12-01 0.9.48 released
 
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 0725033fa600cfb5180ac82283f5194ee35b34b0..f6e6df3a5c0d10fbf1efc2f7069c342c2fe16f4e 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -18,7 +18,7 @@ public class RouterVersion {
     /** deprecated */
     public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 3;
+    public final static long BUILD = 4;
 
     /** for example "-test" */
     public final static String EXTRA = "";
diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java
index 1fd369f796c8ccb793323ab611e4c57893b0642a..380df52c968ae0df51e634f499cce08ffc182303 100644
--- a/router/java/src/net/i2p/router/transport/udp/PeerState.java
+++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java
@@ -1491,7 +1491,8 @@ public class PeerState {
         synchronized(this) {
             retransmitTimer = _retransmitTimer;
         }
-        List<OutboundMessageState> rv = allocateSend2(retransmitTimer > 0 && now >= retransmitTimer, now);
+        boolean canSendOld = retransmitTimer > 0 && now >= retransmitTimer;
+        List<OutboundMessageState> rv = allocateSend2(canSendOld, now);
         if (rv != null && !rv.isEmpty()) {
             synchronized(this) {
                 long old = _retransmitTimer;
@@ -1500,6 +1501,18 @@ public class PeerState {
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug(_remotePeer + " allocated " + rv.size() + " pushing retransmitter from " + old + " to " + _retransmitTimer);
             }
+        } else if (canSendOld) {
+            // failsafe - push out or cancel timer to prevent looping
+            boolean isEmpty;
+            synchronized (_outboundMessages) {
+                isEmpty = _outboundMessages.isEmpty();
+            }
+            synchronized(this) {
+                if (isEmpty)
+                    _retransmitTimer = 0;
+                else
+                    _retransmitTimer = now + 250;
+            }
         }
         return rv;
     }