autobee 2.7.1 → 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 +2 -2
- package/index.js +16 -23
- package/lib/fast-forward.js +23 -10
- package/lib/triggers.js +12 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -285,9 +285,9 @@ Pass one of `head` or `legacy`, not both.
|
|
|
285
285
|
|
|
286
286
|
#### `fastForward.conservative`
|
|
287
287
|
|
|
288
|
-
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.
|
|
289
289
|
|
|
290
|
-
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.
|
|
291
291
|
|
|
292
292
|
#### `await db.moveTo(head, [options])`
|
|
293
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
|
|
|
@@ -828,7 +828,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
828
828
|
|
|
829
829
|
for (const { key, length } of hints) {
|
|
830
830
|
if (length === 0) continue
|
|
831
|
-
promises.push(this._resolveOplogHint(key, length,
|
|
831
|
+
promises.push(this._resolveOplogHint(key, length, { timeout }))
|
|
832
832
|
}
|
|
833
833
|
|
|
834
834
|
const ops = await Promise.all(promises)
|
|
@@ -867,7 +867,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
867
867
|
}
|
|
868
868
|
}
|
|
869
869
|
|
|
870
|
-
async _getOplog(key, length, opts
|
|
870
|
+
async _getOplog(key, length, opts) {
|
|
871
871
|
const core = this.openCore(key)
|
|
872
872
|
|
|
873
873
|
try {
|
|
@@ -878,17 +878,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
878
878
|
}
|
|
879
879
|
|
|
880
880
|
// reads on a core the caller owns, so a walk does not churn a session per step
|
|
881
|
-
async readOplog(core, length,
|
|
881
|
+
async readOplog(core, length, { timeout = 0 } = {}) {
|
|
882
882
|
await core.ready()
|
|
883
883
|
|
|
884
|
-
const { conservative = false, timeout } = opts === null ? {} : opts
|
|
885
|
-
|
|
886
884
|
const target = length >= 0 ? length : core.length
|
|
887
885
|
if (target === 0) return null
|
|
888
886
|
|
|
889
|
-
// conservative: only proceed when a connected peer can serve the head whole
|
|
890
|
-
if (conservative && core.remoteContiguousLength < target) return null
|
|
891
|
-
|
|
892
887
|
const buf = await core.get(target - 1, { timeout })
|
|
893
888
|
if (buf === null) return null
|
|
894
889
|
|
|
@@ -1218,16 +1213,17 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1218
1213
|
return t
|
|
1219
1214
|
}
|
|
1220
1215
|
|
|
1221
|
-
async
|
|
1222
|
-
|
|
1216
|
+
async _processBatch(batch) {
|
|
1217
|
+
// a stack: the tip is pushed in reverse so it pops in order
|
|
1218
|
+
const stack = [batch]
|
|
1223
1219
|
|
|
1224
|
-
while (
|
|
1225
|
-
const batch =
|
|
1220
|
+
while (stack.length) {
|
|
1221
|
+
const batch = stack.pop()
|
|
1226
1222
|
const t = await this.prepareBatch(batch)
|
|
1227
1223
|
|
|
1228
1224
|
if (t.view) {
|
|
1229
1225
|
this._workingBee.move(t.view)
|
|
1230
|
-
|
|
1226
|
+
for (let i = t.tip.length - 1; i >= 0; i--) stack.push(t.tip[i])
|
|
1231
1227
|
continue
|
|
1232
1228
|
}
|
|
1233
1229
|
|
|
@@ -1237,15 +1233,12 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1237
1233
|
|
|
1238
1234
|
await this._applyBatch(batch, batch[0].optimistic)
|
|
1239
1235
|
|
|
1240
|
-
this
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
)
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
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
|
|
1246
1239
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1240
|
+
this.writers.triggers.trigger(id, length)
|
|
1241
|
+
}
|
|
1249
1242
|
}
|
|
1250
1243
|
|
|
1251
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
|
|
@@ -57,7 +56,8 @@ class FastForward {
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
return new FastForward(auto, batchToHead(verified.op.views.system), {
|
|
60
|
-
timeout: timeout || DEFAULT_OP_TIMEOUT
|
|
59
|
+
timeout: timeout || DEFAULT_OP_TIMEOUT,
|
|
60
|
+
conservative
|
|
61
61
|
})
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -68,7 +68,7 @@ class FastForward {
|
|
|
68
68
|
|
|
69
69
|
for (const head of heads) {
|
|
70
70
|
if (head.length === 0) continue
|
|
71
|
-
promises.push(FastForward.flushHead(auto, head,
|
|
71
|
+
promises.push(FastForward.flushHead(auto, head, { timeout }))
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const ops = await Promise.all(promises)
|
|
@@ -180,8 +180,10 @@ class FastForward {
|
|
|
180
180
|
promises.push(this.system.get(this.auto.local.key, { timeout: this.timeout }))
|
|
181
181
|
|
|
182
182
|
// a legacy system without the designated view migrates onto an empty view
|
|
183
|
+
let view = null
|
|
184
|
+
|
|
183
185
|
if (this.system.view.length) {
|
|
184
|
-
|
|
186
|
+
view = this.auto.store.get({ key: this.system.view.key, active: true })
|
|
185
187
|
this.cores.push(view)
|
|
186
188
|
|
|
187
189
|
promises.push(
|
|
@@ -199,6 +201,13 @@ class FastForward {
|
|
|
199
201
|
await Promise.all(promises)
|
|
200
202
|
if (this.destroyed) return null
|
|
201
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
|
+
|
|
202
211
|
return {
|
|
203
212
|
head: this.head,
|
|
204
213
|
migrate: null
|
|
@@ -360,6 +369,10 @@ async function findNewestSystem(ff, info) {
|
|
|
360
369
|
return { legacy, autobee }
|
|
361
370
|
}
|
|
362
371
|
|
|
372
|
+
function isContiguous(core, length) {
|
|
373
|
+
return core.contiguousLength >= length || core.remoteContiguousLength >= length
|
|
374
|
+
}
|
|
375
|
+
|
|
363
376
|
function batchToHead(b) {
|
|
364
377
|
return {
|
|
365
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) {
|