From 12f99ed5c5227bd7308b6ed347c4d677d0c6869b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Thu, 11 Aug 2022 11:58:37 +0200 Subject: [PATCH 01/15] Add `preupgrade` hook sans functionality --- index.js | 12 ++++++++++++ test/preupgrade.js | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 test/preupgrade.js diff --git a/index.js b/index.js index c012564..e48e0b2 100644 --- a/index.js +++ b/index.js @@ -76,6 +76,8 @@ module.exports = class Hypercore extends EventEmitter { this.opening.catch(noop) this._preappend = preappend.bind(this) + this._preupgrade = opts.preupgrade || null + this._preupgrading = null this._snapshot = null this._findingPeers = 0 } @@ -615,6 +617,16 @@ module.exports = class Hypercore extends EventEmitter { let upgraded = await req.promise + if (this._preupgrade) { + if (this._preupgrading === null) { + this._preupgrading = Promise.resolve(this._preupgrade(this)) + this._preupgrading.catch(noop) + } + + await this._preupgrading + this._preupgrading = null + } + if (!this.sparse) { // Download all available blocks in non-sparse mode const start = this.length diff --git a/test/preupgrade.js b/test/preupgrade.js new file mode 100644 index 0000000..ce5e0fe --- /dev/null +++ b/test/preupgrade.js @@ -0,0 +1,18 @@ +const test = require('brittle') +const { create, replicate } = require('./helpers') + +test('preupgrade', async function (t) { + const a = await create() + const b = await create(a.key, { + preupgrade (latest) { + return latest.length + } + }) + + replicate(a, b, t) + + await a.append(['a', 'b', 'c']) + await b.update() + + t.pass() +}) From 409604b27fee02bee134916a8bb866afcbf772d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Thu, 11 Aug 2022 13:31:30 +0200 Subject: [PATCH 02/15] Make it functional --- index.js | 39 +++++++++++++++++++++----- test/preupgrade.js | 68 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index e48e0b2..8ccb171 100644 --- a/index.js +++ b/index.js @@ -76,10 +76,12 @@ module.exports = class Hypercore extends EventEmitter { this.opening.catch(noop) this._preappend = preappend.bind(this) - this._preupgrade = opts.preupgrade || null - this._preupgrading = null this._snapshot = null this._findingPeers = 0 + + this._preupgrade = opts.preupgrade || null + this._preupgradeLength = null + this._postupgradeLength = null } [inspect] (depth, opts) { @@ -449,6 +451,7 @@ module.exports = class Hypercore extends EventEmitter { } get length () { + if (this._preupgradeLength !== null) return this._preupgradeLength if (this._snapshot) return this._snapshot.length if (this.core === null) return 0 if (!this.sparse) return this.contiguousLength @@ -615,16 +618,38 @@ module.exports = class Hypercore extends EventEmitter { const activeRequests = (opts && opts.activeRequests) || this.activeRequests const req = this.replicator.addUpgrade(activeRequests) + if (this._preupgrade && this._preupgradeLength === null) { + this._preupgradeLength = this.core.tree.length + } + let upgraded = await req.promise if (this._preupgrade) { - if (this._preupgrading === null) { - this._preupgrading = Promise.resolve(this._preupgrade(this)) - this._preupgrading.catch(noop) + if (this._postupgradeLength === null) { + const latest = this.session() + + this._postupgradeLength = Promise.resolve(this._preupgrade(latest)) + this._postupgradeLength + .then(() => latest.close()) + .catch(noop) } - await this._preupgrading - this._preupgrading = null + const length = await this._postupgradeLength + const preupgradeLength = this._preupgradeLength + + this._preupgradeLength = null + this._postupgradeLength = null + + if (typeof length === 'number' && length >= preupgradeLength && length < this.core.tree.length) { + this._snapshot = { + length, + byteLength: 0, + fork: this.core.tree.fork, + compatLength: length + } + + return length !== preupgradeLength + } } if (!this.sparse) { diff --git a/test/preupgrade.js b/test/preupgrade.js index ce5e0fe..6462fe7 100644 --- a/test/preupgrade.js +++ b/test/preupgrade.js @@ -1,18 +1,80 @@ const test = require('brittle') const { create, replicate } = require('./helpers') -test('preupgrade', async function (t) { +test('upgrade to latest length', async function (t) { const a = await create() + await a.append(['a', 'b', 'c']) + const b = await create(a.key, { preupgrade (latest) { + t.is(b.length, 0) + t.is(latest.length, 3) + return latest.length } }) replicate(a, b, t) + t.is(b.length, 0) + + t.ok(await b.update()) + t.is(b.length, 3) +}) + +test('stay on previous length', async function (t) { + const a = await create() + await a.append(['a', 'b', 'c']) + + const b = await create(a.key, { + preupgrade (latest) { + t.is(b.length, 0) + t.is(latest.length, 3) + + return 0 + } + }) + + replicate(a, b, t) + t.is(b.length, 0) + + t.absent(await b.update()) + t.is(b.length, 0) +}) +test('return no length', async function (t) { + const a = await create() await a.append(['a', 'b', 'c']) - await b.update() - t.pass() + const b = await create(a.key, { + preupgrade (latest) { + t.is(b.length, 0) + t.is(latest.length, 3) + } + }) + + replicate(a, b, t) + t.is(b.length, 0) + + t.ok(await b.update()) + t.is(b.length, 3) +}) + +test('return invalid length', async function (t) { + const a = await create() + await a.append(['a', 'b', 'c']) + + const b = await create(a.key, { + preupgrade (latest) { + t.is(b.length, 0) + t.is(latest.length, 3) + + return -1 + } + }) + + replicate(a, b, t) + t.is(b.length, 0) + + t.ok(await b.update()) + t.is(b.length, 3) }) From 6f1a7a10217843350b368c804465da499f64b0e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Thu, 11 Aug 2022 14:01:23 +0200 Subject: [PATCH 03/15] Make sure session is always closed --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 8ccb171..0d225de 100644 --- a/index.js +++ b/index.js @@ -630,8 +630,8 @@ module.exports = class Hypercore extends EventEmitter { this._postupgradeLength = Promise.resolve(this._preupgrade(latest)) this._postupgradeLength - .then(() => latest.close()) .catch(noop) + .then(() => latest.close()) } const length = await this._postupgradeLength From 552379a076482a101d21002dcdc6db2209ae2cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Thu, 11 Aug 2022 14:40:01 +0200 Subject: [PATCH 04/15] Simplify preupgrade snapshotting --- index.js | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 0d225de..4fbfb83 100644 --- a/index.js +++ b/index.js @@ -80,8 +80,7 @@ module.exports = class Hypercore extends EventEmitter { this._findingPeers = 0 this._preupgrade = opts.preupgrade || null - this._preupgradeLength = null - this._postupgradeLength = null + this._preupgrading = null } [inspect] (depth, opts) { @@ -451,7 +450,6 @@ module.exports = class Hypercore extends EventEmitter { } get length () { - if (this._preupgradeLength !== null) return this._preupgradeLength if (this._snapshot) return this._snapshot.length if (this.core === null) return 0 if (!this.sparse) return this.contiguousLength @@ -618,27 +616,24 @@ module.exports = class Hypercore extends EventEmitter { const activeRequests = (opts && opts.activeRequests) || this.activeRequests const req = this.replicator.addUpgrade(activeRequests) - if (this._preupgrade && this._preupgradeLength === null) { - this._preupgradeLength = this.core.tree.length - } + if (this._preupgrade) this._updateSnapshot() let upgraded = await req.promise if (this._preupgrade) { - if (this._postupgradeLength === null) { + if (this._preupgrading === null) { const latest = this.session() - this._postupgradeLength = Promise.resolve(this._preupgrade(latest)) - this._postupgradeLength + this._preupgrading = Promise.resolve(this._preupgrade(latest)) + this._preupgrading .catch(noop) .then(() => latest.close()) } - const length = await this._postupgradeLength - const preupgradeLength = this._preupgradeLength + const length = await this._preupgrading + const preupgradeLength = this._snapshot.length - this._preupgradeLength = null - this._postupgradeLength = null + this._preupgrading = null if (typeof length === 'number' && length >= preupgradeLength && length < this.core.tree.length) { this._snapshot = { @@ -650,6 +645,8 @@ module.exports = class Hypercore extends EventEmitter { return length !== preupgradeLength } + + return this._updateSnapshot() } if (!this.sparse) { From eb0079ae1c26b4c83a3fb17aae9f2e99791460ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Thu, 11 Aug 2022 16:51:44 +0200 Subject: [PATCH 05/15] Safer promise clearing --- index.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 4fbfb83..c51b98a 100644 --- a/index.js +++ b/index.js @@ -624,17 +624,22 @@ module.exports = class Hypercore extends EventEmitter { if (this._preupgrading === null) { const latest = this.session() - this._preupgrading = Promise.resolve(this._preupgrade(latest)) + const preupgrading = this._preupgrading = Promise.resolve(this._preupgrade(latest)) + this._preupgrading .catch(noop) - .then(() => latest.close()) + .then(() => { + latest.close() + + if (this._preupgrading === preupgrading) { + this._preupgrading = null + } + }) } const length = await this._preupgrading const preupgradeLength = this._snapshot.length - this._preupgrading = null - if (typeof length === 'number' && length >= preupgradeLength && length < this.core.tree.length) { this._snapshot = { length, From 28841005c07d8f84a468fe16f8afe41523564066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 12 Aug 2022 09:19:18 +0200 Subject: [PATCH 06/15] Clear active preupgrade on truncate --- index.js | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index c51b98a..2012048 100644 --- a/index.js +++ b/index.js @@ -522,6 +522,8 @@ module.exports = class Hypercore extends EventEmitter { // If snapshotted, make sure to update our compat so we can fail gets if (s._snapshot && bitfield.start < s._snapshot.compatLength) s._snapshot.compatLength = bitfield.start + + if (s._preupgrade) s._preupgrading = null } if (s.sparse ? truncated : truncatedNonSparse) { @@ -613,6 +615,8 @@ module.exports = class Hypercore extends EventEmitter { return this._updateSnapshot() } + const fork = this.core.tree.fork + const preupgradeLength = this.core.tree.length const activeRequests = (opts && opts.activeRequests) || this.activeRequests const req = this.replicator.addUpgrade(activeRequests) @@ -621,31 +625,30 @@ module.exports = class Hypercore extends EventEmitter { let upgraded = await req.promise if (this._preupgrade) { - if (this._preupgrading === null) { + let preupgrading = this._preupgrading + if (preupgrading === null) { const latest = this.session() - const preupgrading = this._preupgrading = Promise.resolve(this._preupgrade(latest)) - - this._preupgrading + preupgrading = this._preupgrading = Promise.resolve(this._preupgrade(latest)) + preupgrading .catch(noop) - .then(() => { - latest.close() - - if (this._preupgrading === preupgrading) { - this._preupgrading = null - } - }) + .then(() => latest.close()) } - const length = await this._preupgrading - const preupgradeLength = this._snapshot.length + const length = await preupgrading + + if (this._preupgrading === preupgrading) { + this._preupgrading = null + } if (typeof length === 'number' && length >= preupgradeLength && length < this.core.tree.length) { - this._snapshot = { - length, - byteLength: 0, - fork: this.core.tree.fork, - compatLength: length + if (fork === this.core.tree.fork) { + this._snapshot = { + length, + byteLength: 0, + fork, + compatLength: length + } } return length !== preupgradeLength From 46bc5bb9624aa427bff0275489d3da586de0bd99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 12 Aug 2022 09:48:31 +0200 Subject: [PATCH 07/15] Get `fork` and `preupgradeLength` from snapshot if snapshotted --- index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 2012048..d8dc099 100644 --- a/index.js +++ b/index.js @@ -615,8 +615,9 @@ module.exports = class Hypercore extends EventEmitter { return this._updateSnapshot() } - const fork = this.core.tree.fork - const preupgradeLength = this.core.tree.length + const fork = this.snapshotted ? this._snapshot.fork : this.core.tree.fork + const preupgradeLength = this.snapshotted ? this._snapshot.length : this.core.tree.length + const activeRequests = (opts && opts.activeRequests) || this.activeRequests const req = this.replicator.addUpgrade(activeRequests) From 4e26e8db37e6e706121aadf3fc19b088aa6e958f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 12 Aug 2022 10:59:02 +0200 Subject: [PATCH 08/15] Add first draft of `Upgrader` class --- index.js | 46 ++++--------------------------------- lib/upgrader.js | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 42 deletions(-) create mode 100644 lib/upgrader.js diff --git a/index.js b/index.js index d8dc099..a1f0321 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ const codecs = require('codecs') const Replicator = require('./lib/replicator') const Core = require('./lib/core') const BlockEncryption = require('./lib/block-encryption') +const Upgrader = require('./lib/upgrader') const { ReadStream, WriteStream } = require('./lib/streams') const { BAD_ARGUMENT, SESSION_CLOSED, SESSION_NOT_WRITABLE, SNAPSHOT_NOT_AVAILABLE } = require('./lib/errors') @@ -79,8 +80,7 @@ module.exports = class Hypercore extends EventEmitter { this._snapshot = null this._findingPeers = 0 - this._preupgrade = opts.preupgrade || null - this._preupgrading = null + this._upgrader = new Upgrader(this, opts) } [inspect] (depth, opts) { @@ -523,7 +523,7 @@ module.exports = class Hypercore extends EventEmitter { // If snapshotted, make sure to update our compat so we can fail gets if (s._snapshot && bitfield.start < s._snapshot.compatLength) s._snapshot.compatLength = bitfield.start - if (s._preupgrade) s._preupgrading = null + s._upgrader.ontruncate() } if (s.sparse ? truncated : truncatedNonSparse) { @@ -615,48 +615,10 @@ module.exports = class Hypercore extends EventEmitter { return this._updateSnapshot() } - const fork = this.snapshotted ? this._snapshot.fork : this.core.tree.fork - const preupgradeLength = this.snapshotted ? this._snapshot.length : this.core.tree.length - const activeRequests = (opts && opts.activeRequests) || this.activeRequests const req = this.replicator.addUpgrade(activeRequests) - if (this._preupgrade) this._updateSnapshot() - - let upgraded = await req.promise - - if (this._preupgrade) { - let preupgrading = this._preupgrading - if (preupgrading === null) { - const latest = this.session() - - preupgrading = this._preupgrading = Promise.resolve(this._preupgrade(latest)) - preupgrading - .catch(noop) - .then(() => latest.close()) - } - - const length = await preupgrading - - if (this._preupgrading === preupgrading) { - this._preupgrading = null - } - - if (typeof length === 'number' && length >= preupgradeLength && length < this.core.tree.length) { - if (fork === this.core.tree.fork) { - this._snapshot = { - length, - byteLength: 0, - fork, - compatLength: length - } - } - - return length !== preupgradeLength - } - - return this._updateSnapshot() - } + let upgraded = await this._upgrader.onupgrade(req.promise) if (!this.sparse) { // Download all available blocks in non-sparse mode diff --git a/lib/upgrader.js b/lib/upgrader.js new file mode 100644 index 0000000..0962e5e --- /dev/null +++ b/lib/upgrader.js @@ -0,0 +1,61 @@ +module.exports = class Upgrader { + constructor (session, hooks = {}) { + this._session = session + + this._fork = null + this._length = null + this._latest = null + + this._preupgrade = hooks.preupgrade || null + this._preupgrading = null + } + + async onupgrade (request) { + if (this._preupgrade === null) return request + + const session = this._session + + this._fork = session.snapshotted ? session._snapshot.fork : session.core.tree.fork + this._length = session.snapshotted ? session._snapshot.length : session.core.tree.length + + session._updateSnapshot() + + const upgraded = await request + if (!upgraded) return false + + if (this._fork !== session.core.tree.fork) return session._updateSnapshot() + + let preupgrading = this._preupgrading + if (preupgrading === null) { + const latest = this._latest = session.session() + + preupgrading = this._preupgrading = Promise.resolve(this._preupgrade(latest)) + preupgrading + .catch(noop) + .then(() => latest.close()) + } + + const length = await preupgrading + + this._preupgrading = null + + if (typeof length === 'number' && length >= this._length && length < session.core.tree.length) { + session._snapshot = { + length, + byteLength: 0, + fork: this._fork, + compatLength: length + } + + return length !== this._length + } + + return session._updateSnapshot() + } + + ontruncate () { + // TODO: handle truncation + } +} + +function noop () {} From c5a7b056a6d0d666f6ef5a37cdceaf238795e4a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 12 Aug 2022 11:42:36 +0200 Subject: [PATCH 09/15] Makes upgrades cancelable --- lib/upgrader.js | 85 ++++++++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/lib/upgrader.js b/lib/upgrader.js index 0962e5e..9ceed95 100644 --- a/lib/upgrader.js +++ b/lib/upgrader.js @@ -1,60 +1,81 @@ module.exports = class Upgrader { constructor (session, hooks = {}) { - this._session = session - - this._fork = null - this._length = null - this._latest = null + this.session = session + this.active = null + this.preupgrade = hooks.preupgrade || noop + } - this._preupgrade = hooks.preupgrade || null - this._preupgrading = null + cancel () { + if (this.active) { + this.active.cancel() + this.active = null + } } async onupgrade (request) { - if (this._preupgrade === null) return request + if (this.active) return this.active.upgraded + + const upgrade = this.active = new Upgrade(this) + upgrade.run(request) + + return this.active.upgraded + } + + ontruncate () { + // TODO: handle truncation + } +} - const session = this._session +class Upgrade { + constructor (upgrader) { + this.upgrader = upgrader + this.active = true + this.upgraded = new Promise((resolve) => { + this.done = resolve + }) + } + + cancel () { + this.active = false + } - this._fork = session.snapshotted ? session._snapshot.fork : session.core.tree.fork - this._length = session.snapshotted ? session._snapshot.length : session.core.tree.length + async run (request) { + if (!this.active) return this.done(false) + + const session = this.upgrader.session + + const preupgradeLength = session.length session._updateSnapshot() const upgraded = await request - if (!upgraded) return false - - if (this._fork !== session.core.tree.fork) return session._updateSnapshot() + if (!upgraded || !this.active) return this.done(false) - let preupgrading = this._preupgrading - if (preupgrading === null) { - const latest = this._latest = session.session() + const latest = session.session() - preupgrading = this._preupgrading = Promise.resolve(this._preupgrade(latest)) - preupgrading - .catch(noop) - .then(() => latest.close()) + let length + try { + length = await this.upgrader.preupgrade(latest) + } catch { + length = session.core.tree.length + } finally { + await latest.close() } - const length = await preupgrading + if (!this.active) return this.done(false) - this._preupgrading = null - - if (typeof length === 'number' && length >= this._length && length < session.core.tree.length) { + if (typeof length === 'number' && length >= preupgradeLength && length < session.core.tree.length) { session._snapshot = { length, byteLength: 0, - fork: this._fork, + fork: session.core.tree.fork, compatLength: length } - return length !== this._length + return this.done(length !== this._length) } - return session._updateSnapshot() - } - - ontruncate () { - // TODO: handle truncation + return this.done(session._updateSnapshot()) } } From 6ff6c4d57af2b0ce4d5f7979809162928e5473d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 12 Aug 2022 12:19:19 +0200 Subject: [PATCH 10/15] Fix that --- lib/upgrader.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/upgrader.js b/lib/upgrader.js index 9ceed95..f762268 100644 --- a/lib/upgrader.js +++ b/lib/upgrader.js @@ -43,7 +43,6 @@ class Upgrade { if (!this.active) return this.done(false) const session = this.upgrader.session - const preupgradeLength = session.length session._updateSnapshot() @@ -72,7 +71,7 @@ class Upgrade { compatLength: length } - return this.done(length !== this._length) + return this.done(length !== preupgradeLength) } return this.done(session._updateSnapshot()) From a763308a50ee9255633e3089a029531455d616af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 12 Aug 2022 12:50:17 +0200 Subject: [PATCH 11/15] Move non-sparse handling into upgrader --- index.js | 17 +------------ lib/upgrader.js | 68 ++++++++++++++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/index.js b/index.js index a1f0321..7624be0 100644 --- a/index.js +++ b/index.js @@ -618,22 +618,7 @@ module.exports = class Hypercore extends EventEmitter { const activeRequests = (opts && opts.activeRequests) || this.activeRequests const req = this.replicator.addUpgrade(activeRequests) - let upgraded = await this._upgrader.onupgrade(req.promise) - - if (!this.sparse) { - // Download all available blocks in non-sparse mode - const start = this.length - const end = this.core.tree.length - const contig = this.contiguousLength - - await this.download({ start, end, ifAvailable: true }).downloaded() - - if (!upgraded) upgraded = this.contiguousLength !== contig - } - - if (!upgraded) return false - if (this.snapshotted) return this._updateSnapshot() - return true + return this._upgrader.onupgrade(req.promise) } async seek (bytes, opts) { diff --git a/lib/upgrader.js b/lib/upgrader.js index f762268..71554cb 100644 --- a/lib/upgrader.js +++ b/lib/upgrader.js @@ -2,7 +2,7 @@ module.exports = class Upgrader { constructor (session, hooks = {}) { this.session = session this.active = null - this.preupgrade = hooks.preupgrade || noop + this.preupgrade = hooks.preupgrade || null } cancel () { @@ -12,8 +12,8 @@ module.exports = class Upgrader { } } - async onupgrade (request) { - if (this.active) return this.active.upgraded + onupgrade (request) { + if (this.active) this.active.cancel() const upgrade = this.active = new Upgrade(this) upgrade.run(request) @@ -45,37 +45,53 @@ class Upgrade { const session = this.upgrader.session const preupgradeLength = session.length - session._updateSnapshot() + if (this.upgrader.preupgrade) session._updateSnapshot() - const upgraded = await request - if (!upgraded || !this.active) return this.done(false) + let upgraded = await request - const latest = session.session() + if (!this.active) return this.done(false) - let length - try { - length = await this.upgrader.preupgrade(latest) - } catch { - length = session.core.tree.length - } finally { - await latest.close() - } + if (this.upgrader.preupgrade) { + const latest = session.session() - if (!this.active) return this.done(false) + let length + try { + length = await this.upgrader.preupgrade(latest) + } catch { + length = session.length + } finally { + await latest.close() + } + + if (!this.active) return this.done(false) + + if (typeof length === 'number' && length >= preupgradeLength && length < session.core.tree.length) { + session._snapshot = { + length, + byteLength: 0, + fork: session.core.tree.fork, + compatLength: length + } - if (typeof length === 'number' && length >= preupgradeLength && length < session.core.tree.length) { - session._snapshot = { - length, - byteLength: 0, - fork: session.core.tree.fork, - compatLength: length + return this.done(length !== preupgradeLength) } + } + + if (!session.sparse) { + // Download all available blocks in non-sparse mode + const start = session.length + const end = session.core.tree.length + const contig = session.contiguousLength + + await session.download({ start, end, ifAvailable: true }).downloaded() - return this.done(length !== preupgradeLength) + if (!upgraded) upgraded = session.contiguousLength !== contig } - return this.done(session._updateSnapshot()) + if (!upgraded) return this.done(false) + + if (this.upgrader.preupgrade || session.snapshotted) return this.done(session._updateSnapshot()) + + return this.done(true) } } - -function noop () {} From cb95877c1a47799ada474a77b8a1eafe14015d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 12 Aug 2022 13:16:16 +0200 Subject: [PATCH 12/15] Always await `request` --- lib/upgrader.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/upgrader.js b/lib/upgrader.js index 71554cb..8a4ed84 100644 --- a/lib/upgrader.js +++ b/lib/upgrader.js @@ -40,8 +40,6 @@ class Upgrade { } async run (request) { - if (!this.active) return this.done(false) - const session = this.upgrader.session const preupgradeLength = session.length From 94e4e3a7d133fb2386cbf5d9a69e8e039417a239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 12 Aug 2022 13:16:53 +0200 Subject: [PATCH 13/15] Fix comparison --- lib/upgrader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/upgrader.js b/lib/upgrader.js index 8a4ed84..f07dcb2 100644 --- a/lib/upgrader.js +++ b/lib/upgrader.js @@ -63,7 +63,7 @@ class Upgrade { if (!this.active) return this.done(false) - if (typeof length === 'number' && length >= preupgradeLength && length < session.core.tree.length) { + if (typeof length === 'number' && length >= preupgradeLength && length <= session.core.tree.length) { session._snapshot = { length, byteLength: 0, From 3362190e8422daa0411d5de92ed03be5231c4f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 12 Aug 2022 14:42:29 +0200 Subject: [PATCH 14/15] Add `Snapshot` class --- index.js | 17 +++++------------ lib/snapshot.js | 8 ++++++++ lib/upgrader.js | 9 +++------ 3 files changed, 16 insertions(+), 18 deletions(-) create mode 100644 lib/snapshot.js diff --git a/index.js b/index.js index 7624be0..25ee8df 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ const codecs = require('codecs') const Replicator = require('./lib/replicator') const Core = require('./lib/core') const BlockEncryption = require('./lib/block-encryption') +const Snapshot = require('./lib/snapshot') const Upgrader = require('./lib/upgrader') const { ReadStream, WriteStream } = require('./lib/streams') const { BAD_ARGUMENT, SESSION_CLOSED, SESSION_NOT_WRITABLE, SNAPSHOT_NOT_AVAILABLE } = require('./lib/errors') @@ -345,21 +346,13 @@ module.exports = class Hypercore extends EventEmitter { } _getSnapshot () { + const core = this.core + if (this.sparse) { - return { - length: this.core.tree.length, - byteLength: this.core.tree.byteLength, - fork: this.core.tree.fork, - compatLength: this.core.tree.length - } + return new Snapshot(core.tree.length, core.tree.byteLength, core.tree.fork) } - return { - length: this.core.header.contiguousLength, - byteLength: 0, - fork: this.core.tree.fork, - compatLength: this.core.header.contiguousLength - } + return new Snapshot(core.header.contiguousLength, 0, core.tree.fork) } _updateSnapshot () { diff --git a/lib/snapshot.js b/lib/snapshot.js new file mode 100644 index 0000000..8f67a26 --- /dev/null +++ b/lib/snapshot.js @@ -0,0 +1,8 @@ +module.exports = class Snapshot { + constructor (length, byteLength, fork) { + this.length = length + this.byteLength = byteLength + this.fork = fork + this.compatLength = length + } +} diff --git a/lib/upgrader.js b/lib/upgrader.js index f07dcb2..b226922 100644 --- a/lib/upgrader.js +++ b/lib/upgrader.js @@ -1,3 +1,5 @@ +const Snapshot = require('./snapshot') + module.exports = class Upgrader { constructor (session, hooks = {}) { this.session = session @@ -64,12 +66,7 @@ class Upgrade { if (!this.active) return this.done(false) if (typeof length === 'number' && length >= preupgradeLength && length <= session.core.tree.length) { - session._snapshot = { - length, - byteLength: 0, - fork: session.core.tree.fork, - compatLength: length - } + session._snapshot = new Snapshot(length, 0, session.core.tree.fork) return this.done(length !== preupgradeLength) } From 02b30d5fcf4a6213851e7b588d8694d381084b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Isager=20Dalsgar=C3=B0?= Date: Fri, 12 Aug 2022 14:57:08 +0200 Subject: [PATCH 15/15] Cancel active upgrade if affected by truncate --- lib/upgrader.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/upgrader.js b/lib/upgrader.js index b226922..43ced75 100644 --- a/lib/upgrader.js +++ b/lib/upgrader.js @@ -24,7 +24,14 @@ module.exports = class Upgrader { } ontruncate () { - // TODO: handle truncation + if (this.active === null) return + + const session = this.session + const latest = this.active.latest + + if (latest === null || latest.core.tree.length > session.core.tree.length) { + this.cancel() + } } } @@ -32,6 +39,7 @@ class Upgrade { constructor (upgrader) { this.upgrader = upgrader this.active = true + this.latest = null this.upgraded = new Promise((resolve) => { this.done = resolve }) @@ -52,7 +60,7 @@ class Upgrade { if (!this.active) return this.done(false) if (this.upgrader.preupgrade) { - const latest = session.session() + const latest = this.latest = session.session() let length try { @@ -63,6 +71,8 @@ class Upgrade { await latest.close() } + this.latest = null + if (!this.active) return this.done(false) if (typeof length === 'number' && length >= preupgradeLength && length <= session.core.tree.length) {