From ebd8ec55d715b2458465a7857da304211cadbf55 Mon Sep 17 00:00:00 2001 From: marcus-pousette-hp Date: Tue, 19 May 2026 08:31:23 +0200 Subject: [PATCH 1/2] Avoid reusing destroying connection pool streams --- lib/connect.js | 3 ++- lib/connection-pool.js | 41 +++++++++++++++++++++++++++-------------- test/pool.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/lib/connect.js b/lib/connect.js index 19d232c6..c4e5ce25 100644 --- a/lib/connect.js +++ b/lib/connect.js @@ -44,7 +44,8 @@ module.exports = function connect(dht, publicKey, opts = {}) { ) publicKey = key - if (pool && pool.has(publicKey)) return pool.get(publicKey) + const existing = pool && pool.get(publicKey) + if (existing) return existing publicKey = unslab(publicKey) diff --git a/lib/connection-pool.js b/lib/connection-pool.js index 9dc199a9..c0bd580e 100644 --- a/lib/connection-pool.js +++ b/lib/connection-pool.js @@ -60,16 +60,13 @@ module.exports = class ConnectionPool extends EventEmitter { return } - const session = new ConnectionRef(this, stream) - const keyString = b4a.toString(stream.remotePublicKey, 'hex') + const session = new ConnectionRef(this, keyString, stream) if (opened) { this._connections.set(keyString, session) - stream.on('close', () => { - this._connections.delete(keyString) - }) + stream.on('close', () => session.gc()) this.emit('connection', stream, session) } else { @@ -77,12 +74,10 @@ module.exports = class ConnectionPool extends EventEmitter { stream .on('error', noop) - .on('close', () => { - if (opened) this._connections.delete(keyString) - else this._connecting.delete(keyString) - }) + .on('close', () => session.gc()) .on('open', () => { opened = true + if (this._connecting.get(keyString) !== session) return this._connecting.delete(keyString) this._connections.set(keyString, session) @@ -105,23 +100,27 @@ module.exports = class ConnectionPool extends EventEmitter { } has(publicKey) { - const keyString = b4a.toString(publicKey, 'hex') - - return this._connections.has(keyString) || this._connecting.has(keyString) + return this.get(publicKey) !== null } get(publicKey) { const keyString = b4a.toString(publicKey, 'hex') const existing = this._connections.get(keyString) || this._connecting.get(keyString) + if (!existing) return null + if (existing.destroying) { + existing.gc() + return null + } - return existing?._stream || null + return existing._stream } } class ConnectionRef { - constructor(pool, stream) { + constructor(pool, keyString, stream) { this._pool = pool + this.keyString = keyString this._stream = stream this._refs = 0 } @@ -137,6 +136,20 @@ class ConnectionRef { release() { this._stream.destroy() } + + get destroying() { + return this._stream.destroying || this._stream.destroyed + } + + gc() { + if (this._pool._connections.get(this.keyString) === this) { + this._pool._connections.delete(this.keyString) + } + + if (this._pool._connecting.get(this.keyString) === this) { + this._pool._connecting.delete(this.keyString) + } + } } function noop() {} diff --git a/test/pool.js b/test/pool.js index 6712e1ea..13aa07bd 100644 --- a/test/pool.js +++ b/test/pool.js @@ -1,4 +1,5 @@ const test = require('brittle') +const { once } = require('events') const { swarm } = require('./helpers') test('connection pool, client side', async function (t) { @@ -113,3 +114,34 @@ test('connection pool, client and server side', async function (t) { await server.close() }) + +test('connection pool ignores destroying streams', async function (t) { + const [a, b] = await swarm(t) + + const server = a.createServer((socket) => { + socket.on('error', noop).resume() + }) + + await server.listen() + + const pool = b.pool() + const first = b.connect(server.publicKey, { pool }) + first.on('error', noop) + + await once(first, 'open') + + first.destroy() + t.ok(first.destroying, 'pooled stream is destroying') + + const second = b.connect(server.publicKey, { pool }) + second.on('error', noop) + + t.is(second === first, false, 'destroying stream is not reused') + + second.destroy() + + await a.destroy() + await b.destroy() +}) + +function noop() {} From 0067f0f4890f87cbd84b3eb52d491d982666c843 Mon Sep 17 00:00:00 2001 From: marcus-pousette-hp Date: Tue, 19 May 2026 08:36:33 +0200 Subject: [PATCH 2/2] Clean up connection pool stale stream test --- test/pool.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/pool.js b/test/pool.js index 13aa07bd..c541a874 100644 --- a/test/pool.js +++ b/test/pool.js @@ -140,8 +140,7 @@ test('connection pool ignores destroying streams', async function (t) { second.destroy() - await a.destroy() - await b.destroy() + await server.close() }) function noop() {}