diff --git a/src/ToolSocket.js b/src/ToolSocket.js index 3cd64d5..c68bee1 100644 --- a/src/ToolSocket.js +++ b/src/ToolSocket.js @@ -823,6 +823,22 @@ class ToolSocket { // infoName() keeps the children in sync if the parent is named later if (!toolsocket.parallelSockets) toolsocket.parallelSockets = []; toolsocket.parallelSockets.push(parallel); + // Stop tracking a parallel once it closes. Most parallels are one-shot (the + // proxy closes them after a single request), so a list that only ever grew + // pinned a ToolSocket, its WebSocket, Sender, Receiver and ping Timeout per + // request for the life of the process. A parallel that reconnects re-registers + // itself on 'open', mirroring how the NB layer gates its own teardown. + parallel.addEventListener('close', () => { + const index = toolsocket.parallelSockets.indexOf(parallel); + if (index > -1) { + toolsocket.parallelSockets.splice(index, 1); + } + }); + parallel.addEventListener('open', () => { + if (!toolsocket.parallelSockets.includes(parallel)) { + toolsocket.parallelSockets.push(parallel); + } + }); if (toolsocket.remoteInfoName) { parallel.infoName(toolsocket.remoteInfoName + ' ยท data'); } diff --git a/src/integration.test.js b/src/integration.test.js index 842404d..962c3ba 100644 --- a/src/integration.test.js +++ b/src/integration.test.js @@ -160,4 +160,29 @@ describe('ToolSocket', () => { toolSocket.message('test/binary', { message: 'Binary data' }, null, [binaryData1, binaryData2]); }); + + test('a closed parallel socket stops being tracked by its parent', (done) => { + const parallel = ToolSocket.makeParallelSocket(toolSocket); + + parallel.addEventListener('open', () => { + try { + expect(toolSocket.parallelSockets).toContain(parallel); + } catch (error) { + done(error); + return; + } + parallel.close(); + }); + + parallel.addEventListener('close', () => { + try { + // Parallels are one-shot, so a list that only ever grew pinned the + // whole socket (and its ping timer) once per proxied request + expect(toolSocket.parallelSockets).not.toContain(parallel); + done(); + } catch (error) { + done(error); + } + }); + }); });