From cb352a1c5a816c967f8717296b8ff9a856d63401 Mon Sep 17 00:00:00 2001 From: vheun Date: Mon, 3 Aug 2026 16:03:37 -0400 Subject: [PATCH] Release the NB gc timer when an incoming socket closes enhance() starts a 10s interval to time out stalled transfers and only unref()s it. unref keeps the timer from holding the process open, but the Timeout stays in Node's timer list - a GC root - so the interval's closure pinned nb, sched, opts and the socket itself, with all its eventCallbacks and the dead net.Socket. Every WebSocket a server ever accepted stayed in the heap. Measured against a real ToolSocket server, incoming sockets that connect, subscribe and close: 19,035 B retained per connection at N=200 and 17,417 B at N=600 - linear in connection count. With the close handler: 1,862 B and 237 B, and the total falls from 372 KB to 142 KB between those runs, i.e. no longer proportional to N. Guarded exactly like the pressure-registry entry above it: an auto-reconnecting outgoing socket keeps its timer across reconnects, while an incoming socket (url === null) can never reopen and releases it. large_object_space and external deltas were zero throughout - this is plain old-space retention. --- src/ToolSocketNB.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ToolSocketNB.js b/src/ToolSocketNB.js index 13926bde..1a76b319 100644 --- a/src/ToolSocketNB.js +++ b/src/ToolSocketNB.js @@ -814,7 +814,14 @@ function enhance(ts, userOpts = {}) { } } }, 10000); + // unref() keeps the timer from holding the process open, but it stays in + // the timer list - a GC root - so without this the interval's closure + // pins nb/sched/opts and the whole socket forever. Guarded exactly like + // the pressure entry above: an auto-reconnecting outgoing socket keeps + // its gc timer across reconnects, while an incoming socket (url === null) + // never comes back and must release it. if (gc.unref) gc.unref(); + ts.addEventListener('close', () => { if (!(opts.reconnect && ts.url)) clearInterval(gc); }); // ---------- public backpressure / flow-control API ---------- ts.getBackpressure = () => sched.stats();