autobee 2.7.0 → 2.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/index.js +22 -27
- package/lib/fast-forward.js +29 -14
- package/lib/triggers.js +12 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -256,13 +256,17 @@ Return the oplog head you most recently vouched for, given the `target` view bei
|
|
|
256
256
|
|
|
257
257
|
Called at flush time to stamp your own oplog (with your view as `target` and a `null` reference), and again per candidate during discovery.
|
|
258
258
|
|
|
259
|
+
During discovery `target` is a view opened for the candidate, and reads through it carry the fast-forward's timeout. Anything else the hook awaits is unbounded, so if it reaches beyond that view, put a timeout on it: the candidate waits on the hook, and so does closing it.
|
|
260
|
+
|
|
259
261
|
#### `warmup(view)`
|
|
260
262
|
|
|
261
263
|
Prepare a candidate `view` before the fast-forward onto it is applied.
|
|
262
264
|
|
|
263
265
|
Called as soon as the candidate's view head is known, and runs alongside the block downloads the fast-forward is already doing rather than after them. Throw or reject to reject the candidate: the fast-forward is abandoned and the usual apply path catches up instead.
|
|
264
266
|
|
|
265
|
-
`view` is opened and closed through the `open` and `close` handlers, so don't build or close a db of your own.
|
|
267
|
+
`view` is opened and closed through the `open` and `close` handlers, so don't build or close a db of your own. Reads through it carry the fast-forward's timeout, so a warmup that needs blocks nobody serves fails the candidate instead of hanging.
|
|
268
|
+
|
|
269
|
+
Only reads through `view` are bounded. Anything else the handler awaits, such as other cores or your own network calls, is up to you to time out: the fast-forward waits on the handler, and an abandoned candidate cannot finish closing until it returns.
|
|
266
270
|
|
|
267
271
|
#### `fastForward.boot`
|
|
268
272
|
|
|
@@ -281,9 +285,9 @@ Pass one of `head` or `legacy`, not both.
|
|
|
281
285
|
|
|
282
286
|
#### `fastForward.conservative`
|
|
283
287
|
|
|
284
|
-
Defaults to `true`: only fast-forward
|
|
288
|
+
Defaults to `true`: only fast-forward once the system and view lengths the head stamps are held whole, either locally or by a connected peer.
|
|
285
289
|
|
|
286
|
-
The check
|
|
290
|
+
The check runs at the end of the fast-forward, after the system has booted and the view head has been fetched, so a sparse view nobody can serve is skipped instead of landed on.
|
|
287
291
|
|
|
288
292
|
#### `await db.moveTo(head, [options])`
|
|
289
293
|
|
package/index.js
CHANGED
|
@@ -98,8 +98,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
98
98
|
// oplog head to boot from: migrates or fast-forwards depending on its version
|
|
99
99
|
this.bootFrom = (fastForward && fastForward.boot) || null
|
|
100
100
|
|
|
101
|
-
// conservative (default on): only fast-forward onto a
|
|
102
|
-
this._conservativeFF =
|
|
101
|
+
// conservative (default on): only fast-forward onto a system and view held whole
|
|
102
|
+
this._conservativeFF = fastForward === null || fastForward.conservative !== false
|
|
103
103
|
|
|
104
104
|
this.trusted = new TrustedPeers(this, handlers)
|
|
105
105
|
|
|
@@ -437,15 +437,17 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
// the view an oplog head points at, opened and closed through the handlers
|
|
440
|
-
openViewAt(oplog) {
|
|
440
|
+
openViewAt(oplog, { timeout } = {}) {
|
|
441
441
|
const v = oplog.op.views.view
|
|
442
442
|
if (!v) return null
|
|
443
443
|
|
|
444
|
-
return this.openView({ key: v.key, length: v.start + v.length })
|
|
444
|
+
return this.openView({ key: v.key, length: v.start + v.length }, { timeout })
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
-
|
|
448
|
-
|
|
447
|
+
// timeout bounds every hypercore read done through the view, defaults to the main bee's
|
|
448
|
+
openView(head, { timeout } = {}) {
|
|
449
|
+
const bee = this.bee.checkout({ key: head.key, length: head.length, timeout })
|
|
450
|
+
return new ApplyView(bee, this)
|
|
449
451
|
}
|
|
450
452
|
|
|
451
453
|
openCore(key) {
|
|
@@ -826,7 +828,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
826
828
|
|
|
827
829
|
for (const { key, length } of hints) {
|
|
828
830
|
if (length === 0) continue
|
|
829
|
-
promises.push(this._resolveOplogHint(key, length,
|
|
831
|
+
promises.push(this._resolveOplogHint(key, length, { timeout }))
|
|
830
832
|
}
|
|
831
833
|
|
|
832
834
|
const ops = await Promise.all(promises)
|
|
@@ -865,7 +867,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
865
867
|
}
|
|
866
868
|
}
|
|
867
869
|
|
|
868
|
-
async _getOplog(key, length, opts
|
|
870
|
+
async _getOplog(key, length, opts) {
|
|
869
871
|
const core = this.openCore(key)
|
|
870
872
|
|
|
871
873
|
try {
|
|
@@ -876,17 +878,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
876
878
|
}
|
|
877
879
|
|
|
878
880
|
// reads on a core the caller owns, so a walk does not churn a session per step
|
|
879
|
-
async readOplog(core, length,
|
|
881
|
+
async readOplog(core, length, { timeout = 0 } = {}) {
|
|
880
882
|
await core.ready()
|
|
881
883
|
|
|
882
|
-
const { conservative = false, timeout } = opts === null ? {} : opts
|
|
883
|
-
|
|
884
884
|
const target = length >= 0 ? length : core.length
|
|
885
885
|
if (target === 0) return null
|
|
886
886
|
|
|
887
|
-
// conservative: only proceed when a connected peer can serve the head whole
|
|
888
|
-
if (conservative && core.remoteContiguousLength < target) return null
|
|
889
|
-
|
|
890
887
|
const buf = await core.get(target - 1, { timeout })
|
|
891
888
|
if (buf === null) return null
|
|
892
889
|
|
|
@@ -1216,16 +1213,17 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1216
1213
|
return t
|
|
1217
1214
|
}
|
|
1218
1215
|
|
|
1219
|
-
async
|
|
1220
|
-
|
|
1216
|
+
async _processBatch(batch) {
|
|
1217
|
+
// a stack: the tip is pushed in reverse so it pops in order
|
|
1218
|
+
const stack = [batch]
|
|
1221
1219
|
|
|
1222
|
-
while (
|
|
1223
|
-
const batch =
|
|
1220
|
+
while (stack.length) {
|
|
1221
|
+
const batch = stack.pop()
|
|
1224
1222
|
const t = await this.prepareBatch(batch)
|
|
1225
1223
|
|
|
1226
1224
|
if (t.view) {
|
|
1227
1225
|
this._workingBee.move(t.view)
|
|
1228
|
-
|
|
1226
|
+
for (let i = t.tip.length - 1; i >= 0; i--) stack.push(t.tip[i])
|
|
1229
1227
|
continue
|
|
1230
1228
|
}
|
|
1231
1229
|
|
|
@@ -1235,15 +1233,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1235
1233
|
|
|
1236
1234
|
await this._applyBatch(batch, batch[0].optimistic)
|
|
1237
1235
|
|
|
1238
|
-
this
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
)
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1236
|
+
// trigger any upstream writer waiting for this batch early
|
|
1237
|
+
const id = b4a.toString(batch[0].key, 'hex')
|
|
1238
|
+
const length = batch[batch.length - 1].length
|
|
1244
1239
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1240
|
+
this.writers.triggers.trigger(id, length)
|
|
1241
|
+
}
|
|
1247
1242
|
}
|
|
1248
1243
|
|
|
1249
1244
|
async _applyBatch(batch, optimistic) {
|
package/lib/fast-forward.js
CHANGED
|
@@ -10,9 +10,12 @@ const DEFAULT_OP_TIMEOUT = 5_000
|
|
|
10
10
|
const MIN_FF_GAP = 32
|
|
11
11
|
|
|
12
12
|
class FastForward {
|
|
13
|
-
constructor(auto, head, { timeout = DEFAULT_OP_TIMEOUT } = {}) {
|
|
13
|
+
constructor(auto, head, { timeout = DEFAULT_OP_TIMEOUT, conservative = false } = {}) {
|
|
14
14
|
this.auto = auto
|
|
15
15
|
|
|
16
|
+
// conservative: only land when the system and view we booted are held whole
|
|
17
|
+
this.conservative = conservative
|
|
18
|
+
|
|
16
19
|
this.activeRequests = []
|
|
17
20
|
|
|
18
21
|
// 'reboot' is a storage namespace: renaming it would derive a different
|
|
@@ -38,15 +41,11 @@ class FastForward {
|
|
|
38
41
|
|
|
39
42
|
static async fromHead(auto, head, trusted, { force = false, timeout = 0 } = {}) {
|
|
40
43
|
const conservative = !force && auto._conservativeFF
|
|
41
|
-
const timeoutOpts = timeout ? { timeout } : null
|
|
42
44
|
|
|
43
|
-
const oplog = await FastForward.flushHead(auto, head, {
|
|
44
|
-
conservative,
|
|
45
|
-
...timeoutOpts
|
|
46
|
-
})
|
|
45
|
+
const oplog = await FastForward.flushHead(auto, head, { timeout })
|
|
47
46
|
if (!oplog) return null
|
|
48
47
|
|
|
49
|
-
const verified = trusted ? await FastForward.flushHead(auto, trusted,
|
|
48
|
+
const verified = trusted ? await FastForward.flushHead(auto, trusted, { timeout }) : oplog
|
|
50
49
|
if (!verified) return null
|
|
51
50
|
|
|
52
51
|
// legacy nodes from non-indexers have no system info to fast-forward from
|
|
@@ -56,7 +55,10 @@ class FastForward {
|
|
|
56
55
|
return null
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
return new FastForward(auto, batchToHead(verified.op.views.system)
|
|
58
|
+
return new FastForward(auto, batchToHead(verified.op.views.system), {
|
|
59
|
+
timeout: timeout || DEFAULT_OP_TIMEOUT,
|
|
60
|
+
conservative
|
|
61
|
+
})
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
static async fromHeads(auto, heads, { force = false, timeout = 0 } = {}) {
|
|
@@ -66,7 +68,7 @@ class FastForward {
|
|
|
66
68
|
|
|
67
69
|
for (const head of heads) {
|
|
68
70
|
if (head.length === 0) continue
|
|
69
|
-
promises.push(FastForward.flushHead(auto, head,
|
|
71
|
+
promises.push(FastForward.flushHead(auto, head, { timeout }))
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
const ops = await Promise.all(promises)
|
|
@@ -107,7 +109,7 @@ class FastForward {
|
|
|
107
109
|
candidates.sort((a, b) => b.op.views.flushes - a.op.views.flushes)
|
|
108
110
|
|
|
109
111
|
for (const res of candidates) {
|
|
110
|
-
const trusted = await FastForward.mostRecentTrusted(auto, res, reference)
|
|
112
|
+
const trusted = await FastForward.mostRecentTrusted(auto, res, reference, timeout)
|
|
111
113
|
if (trusted === null) continue
|
|
112
114
|
if (auto.fastForwarding || auto.fastForwardTo) return null
|
|
113
115
|
|
|
@@ -130,8 +132,8 @@ class FastForward {
|
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
static async mostRecentTrusted(auto, head, reference) {
|
|
134
|
-
const opened = auto.openViewAt(head)
|
|
135
|
+
static async mostRecentTrusted(auto, head, reference, timeout) {
|
|
136
|
+
const opened = auto.openViewAt(head, { timeout })
|
|
135
137
|
if (opened === null) return null
|
|
136
138
|
|
|
137
139
|
try {
|
|
@@ -178,8 +180,10 @@ class FastForward {
|
|
|
178
180
|
promises.push(this.system.get(this.auto.local.key, { timeout: this.timeout }))
|
|
179
181
|
|
|
180
182
|
// a legacy system without the designated view migrates onto an empty view
|
|
183
|
+
let view = null
|
|
184
|
+
|
|
181
185
|
if (this.system.view.length) {
|
|
182
|
-
|
|
186
|
+
view = this.auto.store.get({ key: this.system.view.key, active: true })
|
|
183
187
|
this.cores.push(view)
|
|
184
188
|
|
|
185
189
|
promises.push(
|
|
@@ -197,6 +201,13 @@ class FastForward {
|
|
|
197
201
|
await Promise.all(promises)
|
|
198
202
|
if (this.destroyed) return null
|
|
199
203
|
|
|
204
|
+
// conservative: the lengths we land on must be held whole, either by us or
|
|
205
|
+
// by a connected peer, so the view does not stall on a block nobody has
|
|
206
|
+
if (this.conservative) {
|
|
207
|
+
if (!isContiguous(this.system.bee.core, this.head.length)) return null
|
|
208
|
+
if (view !== null && !isContiguous(view, this.system.view.length)) return null
|
|
209
|
+
}
|
|
210
|
+
|
|
200
211
|
return {
|
|
201
212
|
head: this.head,
|
|
202
213
|
migrate: null
|
|
@@ -206,7 +217,7 @@ class FastForward {
|
|
|
206
217
|
_startWarmup() {
|
|
207
218
|
if (!this.auto._warmup || !this.system.view.length) return false
|
|
208
219
|
|
|
209
|
-
this.warmupView = this.auto.openView(this.system.view)
|
|
220
|
+
this.warmupView = this.auto.openView(this.system.view, { timeout: this.timeout })
|
|
210
221
|
|
|
211
222
|
// ensure thenable
|
|
212
223
|
this.warmup = Promise.resolve(this.auto._warmup(this.warmupView.view))
|
|
@@ -358,6 +369,10 @@ async function findNewestSystem(ff, info) {
|
|
|
358
369
|
return { legacy, autobee }
|
|
359
370
|
}
|
|
360
371
|
|
|
372
|
+
function isContiguous(core, length) {
|
|
373
|
+
return core.contiguousLength >= length || core.remoteContiguousLength >= length
|
|
374
|
+
}
|
|
375
|
+
|
|
361
376
|
function batchToHead(b) {
|
|
362
377
|
return {
|
|
363
378
|
key: b.key,
|
package/lib/triggers.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
// writers waiting on a node of another writer, keyed by that writer's id.
|
|
2
|
+
// a writer waits on at most one entry at a time, tracked as writer.waiting
|
|
1
3
|
module.exports = class Triggers {
|
|
2
4
|
constructor() {
|
|
3
5
|
this.waiting = new Map()
|
|
4
6
|
}
|
|
5
7
|
|
|
6
8
|
add(id, length, writer) {
|
|
9
|
+
// a writer only ever waits on one link: drop whatever it waited on before
|
|
10
|
+
if (writer.waiting !== null && writer.waiting.id !== id) this.remove(writer.waiting)
|
|
11
|
+
|
|
7
12
|
let w = this.waiting.get(id)
|
|
8
13
|
|
|
9
14
|
if (!w) {
|
|
@@ -24,6 +29,7 @@ module.exports = class Triggers {
|
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
remove(entry) {
|
|
32
|
+
// writer is nulled on removal, so a removed entry is a noop here
|
|
27
33
|
if (!entry.writer) return
|
|
28
34
|
|
|
29
35
|
const w = this.waiting.get(entry.id)
|
|
@@ -42,14 +48,18 @@ module.exports = class Triggers {
|
|
|
42
48
|
const w = this.waiting.get(id)
|
|
43
49
|
if (!w) return
|
|
44
50
|
|
|
51
|
+
// collect first: the walk swap-removes, so nothing may touch the array mid-loop
|
|
52
|
+
const bump = []
|
|
53
|
+
|
|
45
54
|
for (let i = w.length - 1; i >= 0; i--) {
|
|
46
55
|
const entry = w[i]
|
|
47
56
|
if (entry.length > length) continue
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
bump.push(entry.writer)
|
|
50
59
|
this._remove(w, entry)
|
|
51
|
-
writer.bump()
|
|
52
60
|
}
|
|
61
|
+
|
|
62
|
+
for (const writer of bump) writer.bump()
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
_remove(w, entry) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autobee",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.2",
|
|
4
4
|
"description": "Bee based autobase",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Holepunch Inc.",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"autobee-wakeup": "^1.1.3",
|
|
28
28
|
"b4a": "^1.8.0",
|
|
29
29
|
"compact-encoding": "^3.3.0",
|
|
30
|
-
"hyperbee2": "^2.
|
|
30
|
+
"hyperbee2": "^2.16.0",
|
|
31
31
|
"hypercore": "^11.33.2",
|
|
32
32
|
"hypercore-id-encoding": "^1.3.0",
|
|
33
33
|
"hyperschema": "^1.20.1",
|