From 604c7449f09607c3b5afbe743af2e52641a209d0 Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Thu, 31 Jul 2025 11:34:29 +0100 Subject: [PATCH 1/8] test that inactive session creates inactive session --- test/replicate.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/replicate.js b/test/replicate.js index 298bc0f23..666545bd2 100644 --- a/test/replicate.js +++ b/test/replicate.js @@ -132,6 +132,39 @@ test('basic downloading is set immediately after ready', async function (t) { }) }) +test('basic session on inactive core is inactive', async function (t) { + t.plan(4) + + const createA = await createStored(t) + const a = await createA() + + a.on('ready', function () { + t.ok(a.core.replicator.downloading) + }) + + const createB = await createStored(t) + const b = await createB({ active: false }) + + b.on('ready', function () { + t.absent(b.core.replicator.downloading) + + const c = b.session() + c.debug = true + + t.teardown(() => c.close()) + + c.on('ready', function () { + t.absent(b.core.replicator.downloading) + t.absent(c.core.replicator.downloading) + }) + }) + + t.teardown(async () => { + await a.close() + await b.close() + }) +}) + test('basic replication from fork', async function (t) { const a = await create(t) From 4869ad2c59e7300ef0a78c16df6b0727b684501f Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Thu, 31 Jul 2025 11:35:45 +0100 Subject: [PATCH 2/8] extend test to check reactivating --- test/replicate.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/replicate.js b/test/replicate.js index 666545bd2..b8ea5b288 100644 --- a/test/replicate.js +++ b/test/replicate.js @@ -133,7 +133,7 @@ test('basic downloading is set immediately after ready', async function (t) { }) test('basic session on inactive core is inactive', async function (t) { - t.plan(4) + t.plan(5) const createA = await createStored(t) const a = await createA() @@ -156,6 +156,12 @@ test('basic session on inactive core is inactive', async function (t) { c.on('ready', function () { t.absent(b.core.replicator.downloading) t.absent(c.core.replicator.downloading) + + const d = c.session({ active: true }) + + d.on('ready', function () { + t.ok(d.core.replicator.downloading) + }) }) }) From 7da0111d808aa5947c21ebee239bec73fa91d370 Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Thu, 31 Jul 2025 11:35:59 +0100 Subject: [PATCH 3/8] session inherits activity by default --- index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.js b/index.js index 647be5395..24e96881c 100644 --- a/index.js +++ b/index.js @@ -223,6 +223,7 @@ class Hypercore extends EventEmitter { const onseq = opts.onseq === undefined ? this.onseq : opts.onseq const timeout = opts.timeout === undefined ? this.timeout : opts.timeout const weak = opts.weak === undefined ? this.weak : opts.weak + const active = opts.active === undefined ? this._active : opts.active const Clz = opts.class || Hypercore const s = new Clz(null, this.key, { ...opts, @@ -232,6 +233,7 @@ class Hypercore extends EventEmitter { timeout, writable, weak, + active, parent: this }) From d41997c6f0128f33bc9d7630f760fe99ffb3cb5c Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Thu, 31 Jul 2025 11:51:17 +0100 Subject: [PATCH 4/8] fix up test --- test/replicate.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/replicate.js b/test/replicate.js index b8ea5b288..fe21e4332 100644 --- a/test/replicate.js +++ b/test/replicate.js @@ -149,8 +149,6 @@ test('basic session on inactive core is inactive', async function (t) { t.absent(b.core.replicator.downloading) const c = b.session() - c.debug = true - t.teardown(() => c.close()) c.on('ready', function () { @@ -158,6 +156,7 @@ test('basic session on inactive core is inactive', async function (t) { t.absent(c.core.replicator.downloading) const d = c.session({ active: true }) + t.teardown(() => d.close()) d.on('ready', function () { t.ok(d.core.replicator.downloading) From b3a8ad1c916eee06540c5a4fcb1df3458ce70c10 Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Thu, 31 Jul 2025 12:35:51 +0100 Subject: [PATCH 5/8] only main sessions are ever active --- index.js | 2 ++ test/replicate.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/index.js b/index.js index 24e96881c..e49c2fcc9 100644 --- a/index.js +++ b/index.js @@ -417,6 +417,8 @@ class Hypercore extends EventEmitter { await this.core.setManifest(createManifest(opts.manifest)) } + if (this.state !== this.core.state) this._active = false + this.core.replicator.updateActivity(this._active ? 1 : 0) this.opened = true diff --git a/test/replicate.js b/test/replicate.js index fe21e4332..9f1abd37f 100644 --- a/test/replicate.js +++ b/test/replicate.js @@ -170,6 +170,41 @@ test('basic session on inactive core is inactive', async function (t) { }) }) +test('basic named session is always inactive', async function (t) { + t.plan(4) + + const createA = await createStored(t) + const a = await createA() + + a.on('ready', function () { + t.ok(a.core.replicator.downloading) + }) + + const createB = await createStored(t) + const b = await createB({ + notDownloadingLinger: 0 // replicator activity updates immediately + }) + + b.on('ready', function () { + t.ok(b.core.replicator.downloading) + + const c = b.session({ name: 'named' }) + t.teardown(() => c.close()) + + c.on('ready', async function () { + b.setActive(false) + + t.absent(b.core.replicator.downloading) + t.absent(c.core.replicator.downloading) + }) + }) + + t.teardown(async () => { + await a.close() + await b.close() + }) +}) + test('basic replication from fork', async function (t) { const a = await create(t) From 41d35943e19ec39b60c995e00360b0dfe8b30be2 Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Thu, 31 Jul 2025 18:56:45 +0100 Subject: [PATCH 6/8] do not create new peers if not downloading --- lib/replicator.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/replicator.js b/lib/replicator.js index 4c2517f11..d66af5fbb 100644 --- a/lib/replicator.js +++ b/lib/replicator.js @@ -2461,6 +2461,8 @@ module.exports = class Replicator { } _makePeer (protomux) { + if (!this.downloading) return false + const replicator = this if (protomux.opened({ protocol: 'hypercore/alpha', id: this.core.discoveryKey })) return onnochannel() From f64fb1128c855f12843a0708687fbf9e98c78f0f Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Fri, 1 Aug 2025 11:25:49 +0100 Subject: [PATCH 7/8] add test for toggle active with existing connection --- test/replicate.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/replicate.js b/test/replicate.js index 9f1abd37f..b7bcd6bee 100644 --- a/test/replicate.js +++ b/test/replicate.js @@ -205,6 +205,39 @@ test('basic named session is always inactive', async function (t) { }) }) +test('basic toggle active with existing connection', async function (t) { + t.plan(4) + + const a = await create(t) + await a.ready() + + const b = await create(t, a.key, { active: false }) + + await b.ready() + + t.absent(b.core.replicator.downloading) + + replicate(a, b, t) + + await a.append('a1') + + setTimeout(() => { + t.is(b.length, 0) + + b.setActive(true) + t.ok(b.core.replicator.downloading) + + b.on('append', () => { + t.is(b.length, 1) + }) + }, 1000) + + t.teardown(async () => { + await a.close() + await b.close() + }) +}) + test('basic replication from fork', async function (t) { const a = await create(t) From 5e10668e2534f147782958a71d7282b07ed84508 Mon Sep 17 00:00:00 2001 From: Christophe Diederichs Date: Fri, 1 Aug 2025 12:11:49 +0100 Subject: [PATCH 8/8] bypass linger if we have no peers --- lib/replicator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/replicator.js b/lib/replicator.js index d66af5fbb..77f8838ee 100644 --- a/lib/replicator.js +++ b/lib/replicator.js @@ -1541,7 +1541,7 @@ module.exports = class Replicator { clearTimeout(this._downloadingTimer) if (this.destroyed) return - if (downloading || this._notDownloadingLinger === 0) { + if (downloading || this._notDownloadingLinger === 0 || !this.peers.length) { this.setDownloadingNow(downloading) return }