Skip to content
This repository has been archived by the owner on Dec 28, 2022. It is now read-only.

Add preupgrade hook #129

Closed
wants to merge 15 commits into from
Closed
22 changes: 6 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -78,6 +79,8 @@ module.exports = class Hypercore extends EventEmitter {
this._preappend = preappend.bind(this)
this._snapshot = null
this._findingPeers = 0

this._upgrader = new Upgrader(this, opts)
}

[inspect] (depth, opts) {
Expand Down Expand Up @@ -519,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

s._upgrader.ontruncate()
}

if (s.sparse ? truncated : truncatedNonSparse) {
Expand Down Expand Up @@ -613,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 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) {
Expand Down
97 changes: 97 additions & 0 deletions lib/upgrader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
module.exports = class Upgrader {
constructor (session, hooks = {}) {
this.session = session
this.active = null
this.preupgrade = hooks.preupgrade || null
}

cancel () {
if (this.active) {
this.active.cancel()
this.active = null
}
}

onupgrade (request) {
if (this.active) this.active.cancel()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that only 1 upgrade is active at a time by cancelling previous upgrades as new ones are applied. Not 100% sure if that's what we're after though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either this, or it "debounces", ie reruns the hook


const upgrade = this.active = new Upgrade(this)
upgrade.run(request)

return this.active.upgraded
}

ontruncate () {
// TODO: handle truncation
}
}

class Upgrade {
constructor (upgrader) {
this.upgrader = upgrader
this.active = true
this.upgraded = new Promise((resolve) => {
this.done = resolve
})
}

cancel () {
this.active = false
}

async run (request) {
if (!this.active) return this.done(false)
kasperisager marked this conversation as resolved.
Show resolved Hide resolved

const session = this.upgrader.session
const preupgradeLength = session.length

if (this.upgrader.preupgrade) session._updateSnapshot()

let upgraded = await request
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats stopping us from awating the request in update itself? seems cleaner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to do session._updateSnapshot() before the update is applied to the core to keep the session on the preupgrade length.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is session here, the session the user called .update on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep (this):

this._upgrader = new Upgrader(this, opts)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will see what we can do to improve this, but ok for now.


if (!this.active) return this.done(false)

if (this.upgrader.preupgrade) {
const latest = session.session()

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) {
kasperisager marked this conversation as resolved.
Show resolved Hide resolved
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()

if (!upgraded) upgraded = session.contiguousLength !== contig
}

if (!upgraded) return this.done(false)

if (this.upgrader.preupgrade || session.snapshotted) return this.done(session._updateSnapshot())

return this.done(true)
}
}
80 changes: 80 additions & 0 deletions test/preupgrade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const test = require('brittle')
const { create, replicate } = require('./helpers')

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'])

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)
})