From a957213e7d09306e94603f00a93c90bb681eed09 Mon Sep 17 00:00:00 2001 From: marcus-pousette-hp Date: Mon, 18 May 2026 22:39:06 +0200 Subject: [PATCH] Skip idle close peer update scans --- lib/replicator.js | 28 ++++++++++++++++++++++++--- test/replicate.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/lib/replicator.js b/lib/replicator.js index 369a6f08..18d408ed 100644 --- a/lib/replicator.js +++ b/lib/replicator.js @@ -754,7 +754,7 @@ class Peer { if (this.remoteOpened === false) { this.replicator._ifAvailable-- - this.replicator.updateAll() + this.replicator._maybeResolveAvailable() return } @@ -2449,16 +2449,33 @@ module.exports = class Replicator { this.peers.splice(this.peers.indexOf(peer), 1) peer.wants.destroy() - if (this._manifestPeer === peer) this._manifestPeer = null + let needsUpdate = false + + if (this._manifestPeer === peer) { + this._manifestPeer = null + needsUpdate = true + } for (const req of this._inflight) { if (req.peer !== peer) continue this._inflight.remove(req.id, true) this._clearRequest(peer, req) + needsUpdate = true } this._onpeerupdate(false, peer) - this.updateAll() + + // A close can also wake pending work that was not assigned to the closing peer. + const hasPendingUpdate = + needsUpdate || + this._queued.length > 0 || + this._seeks.length > 0 || + this._ranges.length > 0 || + this._reorgs.length > 0 || + this._maybeUpdate() + + if (hasPendingUpdate && this.peers.length > 0) this.updateAll() + else this._maybeResolveAvailable() } _queueBlock(b) { @@ -2668,6 +2685,11 @@ module.exports = class Replicator { } } + _maybeResolveAvailable() { + this._checkUpgradeIfAvailable() + this._maybeResolveIfAvailableRanges() + } + _clearRequest(peer, req) { if (req.block !== null) { this._clearInflightBlock(this._blocks, req) diff --git a/test/replicate.js b/test/replicate.js index 20b1b02f..d354c333 100644 --- a/test/replicate.js +++ b/test/replicate.js @@ -844,6 +844,55 @@ test('closing peer with inflight block reschedules on remaining peer', async fun t.alike(await block, b4a.from('block-0')) }) +test('closing idle multiplexed stream does not update all peers', async function (t) { + const writer = await create(t) + await writer.append('block') + + const clone = await create(t, writer.key) + const streams = [] + + for (let i = 0; i < 3; i++) { + const peer = new Promise((resolve) => clone.once('peer-add', resolve)) + streams.push(replicate(writer, clone, t, { keepAlive: true })) + await peer + } + + await clone.get(0) + await eventFlush() + + t.is(clone.peers.length, 3, 'clone has multiple peers') + + const replicator = clone.core.replicator + const updateAll = replicator.updateAll + const updatePeer = replicator._updatePeer + + let updates = 0 + let peerScans = 0 + + replicator.updateAll = function () { + updates++ + return updateAll.apply(this, arguments) + } + + replicator._updatePeer = function () { + peerScans++ + return updatePeer.apply(this, arguments) + } + + t.teardown(() => { + replicator.updateAll = updateAll + replicator._updatePeer = updatePeer + }) + + const peerRemoved = new Promise((resolve) => clone.once('peer-remove', resolve)) + await unreplicate(streams[0]) + await peerRemoved + + t.is(clone.peers.length, 2, 'clone still has remaining peers') + t.is(updates, 0) + t.is(peerScans, 0) +}) + test('closing idle peer schedules pending range on remaining peer', async function (t) { const writer = await create(t) const batch = []