autobee 2.0.0-rc.13 → 2.0.0-rc.15
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/index.js +68 -42
- package/lib/boot.js +1 -0
- package/lib/{reboot.js → fast-forward.js} +35 -5
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -14,7 +14,7 @@ const asserts = require('./lib/asserts.js')
|
|
|
14
14
|
const boot = require('./lib/boot.js')
|
|
15
15
|
const { resolveWeight, currentWeight } = require('./lib/witness.js')
|
|
16
16
|
const encoding = require('./lib/encoding.js')
|
|
17
|
-
const
|
|
17
|
+
const FastForward = require('./lib/fast-forward.js')
|
|
18
18
|
const System = require('./lib/system.js')
|
|
19
19
|
const ApplyCalls = require('./lib/apply-calls.js')
|
|
20
20
|
const topo = require('./lib/topo.js')
|
|
@@ -57,6 +57,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
57
57
|
this.id = null
|
|
58
58
|
this.bootstrap = null
|
|
59
59
|
this.handlers = handlers
|
|
60
|
+
this.stats = { undos: 0, fastForwards: 0, drains: 0, applies: 0, appends: 0 }
|
|
60
61
|
|
|
61
62
|
this.system = new System(this.store.namespace('system'), this.name, {
|
|
62
63
|
getEncryptionProvider: this.getSystemEncryption,
|
|
@@ -84,9 +85,9 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
84
85
|
// conservative (default on): only fast-forward onto a head someone can serve whole
|
|
85
86
|
this._conservativeFF = fastForward.conservative !== false
|
|
86
87
|
|
|
87
|
-
this.
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
88
|
+
this.ff = null
|
|
89
|
+
this.fastForwarding = null
|
|
90
|
+
this.fastForwardTo = null
|
|
90
91
|
|
|
91
92
|
this._workingBee = bee
|
|
92
93
|
this._workingView = handlers.open ? handlers.open(this._workingBee, this) : this._workingBee
|
|
@@ -249,6 +250,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
249
250
|
session.clearRequests(session.activeRequests, closingError)
|
|
250
251
|
}
|
|
251
252
|
}
|
|
253
|
+
|
|
254
|
+
if (this.fastForwarding) this.fastForwarding.clearRequests(closingError)
|
|
252
255
|
}
|
|
253
256
|
|
|
254
257
|
async changesFrom({ system } = {}) {
|
|
@@ -440,8 +443,6 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
440
443
|
async _bump() {
|
|
441
444
|
if (!(await this._bootReady())) return
|
|
442
445
|
|
|
443
|
-
await this._flushWakeup()
|
|
444
|
-
|
|
445
446
|
this.bumping++
|
|
446
447
|
|
|
447
448
|
if (!this._draining) {
|
|
@@ -498,6 +499,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
498
499
|
async _drain() {
|
|
499
500
|
if (this._updating) await this._updating
|
|
500
501
|
|
|
502
|
+
this.stats.drains++
|
|
503
|
+
|
|
501
504
|
if (this.bootFrom) {
|
|
502
505
|
await this._initFromHead(this.bootFrom)
|
|
503
506
|
this.bootFrom = null
|
|
@@ -522,8 +525,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
522
525
|
|
|
523
526
|
try {
|
|
524
527
|
while (!this._interrupting) {
|
|
525
|
-
|
|
526
|
-
|
|
528
|
+
await this._flushWakeup()
|
|
529
|
+
if (this._interrupting) break
|
|
530
|
+
|
|
531
|
+
if (this.fastForwardTo !== null) {
|
|
532
|
+
await this._applyFastForward()
|
|
527
533
|
break // revaluate conditions...
|
|
528
534
|
}
|
|
529
535
|
|
|
@@ -590,15 +596,28 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
590
596
|
|
|
591
597
|
async _flushWakeup() {
|
|
592
598
|
const hints = this._wakeup.flush()
|
|
599
|
+
if (!hints.size) return
|
|
593
600
|
|
|
594
601
|
this.previousDrain = Date.now()
|
|
595
|
-
this.rebootFromHeads(hints).catch(noop)
|
|
596
602
|
|
|
597
603
|
for (const [hex, length] of hints) {
|
|
598
604
|
const key = b4a.from(hex, 'hex')
|
|
599
605
|
if (this.writers.has(hex)) continue
|
|
600
606
|
await this.writers.wakeup(key, length)
|
|
601
607
|
}
|
|
608
|
+
|
|
609
|
+
// a scheduled fast-forward is applied by the drain before we look again
|
|
610
|
+
if (this.fastForwardTo !== null) return
|
|
611
|
+
if (this._interrupting || this.closing) return
|
|
612
|
+
|
|
613
|
+
try {
|
|
614
|
+
await this.fastForwardFromHeads(hints, { wait: false, timeout: FastForward.DEFAULT_TIMEOUT })
|
|
615
|
+
} catch (err) {
|
|
616
|
+
safetyCatch(err)
|
|
617
|
+
return false
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
return true
|
|
602
621
|
}
|
|
603
622
|
|
|
604
623
|
async _getOplog(key, length, opts = null) {
|
|
@@ -731,7 +750,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
731
750
|
await this.bootstrap.setUserData('autobee/local', this.local.key)
|
|
732
751
|
await oldLocal.close()
|
|
733
752
|
|
|
734
|
-
// done, soft
|
|
753
|
+
// done, soft restart
|
|
735
754
|
this.emit('rotate-local-writer')
|
|
736
755
|
}
|
|
737
756
|
|
|
@@ -919,6 +938,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
919
938
|
const t = await topo.sort(this, batch)
|
|
920
939
|
|
|
921
940
|
if (t.undo) {
|
|
941
|
+
this.stats.undos++
|
|
922
942
|
t.view = await this.system.undo(t.undo)
|
|
923
943
|
}
|
|
924
944
|
|
|
@@ -967,6 +987,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
967
987
|
}
|
|
968
988
|
|
|
969
989
|
if (this._hasApply && (await this.system.canApply(batch[0].key, optimistic))) {
|
|
990
|
+
this.stats.applies++
|
|
970
991
|
this._host.applying = batch
|
|
971
992
|
await this._handlers.apply(userBatch, this._workingView, this._host)
|
|
972
993
|
this._host.applying = null
|
|
@@ -1035,6 +1056,8 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1035
1056
|
async append(values, { optimistic = false } = {}) {
|
|
1036
1057
|
if (this.closing) throw new Error('Autobee closed')
|
|
1037
1058
|
|
|
1059
|
+
this.stats.appends++
|
|
1060
|
+
|
|
1038
1061
|
if (!Array.isArray(values)) values = [values]
|
|
1039
1062
|
|
|
1040
1063
|
if (!this.opened) await this.ready()
|
|
@@ -1112,10 +1135,10 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1112
1135
|
this._localViewLength = 0
|
|
1113
1136
|
}
|
|
1114
1137
|
|
|
1115
|
-
async
|
|
1138
|
+
async fastForwardFromHeads(hints, { wait = true, timeout = 0 } = {}) {
|
|
1116
1139
|
if (!this._handlers.onwakeup) return false
|
|
1117
1140
|
if (this._interrupting) return false
|
|
1118
|
-
if (!hints.size || this.
|
|
1141
|
+
if (!hints.size || this.fastForwarding || this.fastForwardTo || this.bootFrom) {
|
|
1119
1142
|
return false
|
|
1120
1143
|
}
|
|
1121
1144
|
|
|
@@ -1125,11 +1148,11 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1125
1148
|
if (length === 0) continue
|
|
1126
1149
|
const key = b4a.from(hex, 'hex')
|
|
1127
1150
|
heads.push({ key, length })
|
|
1128
|
-
promises.push(this._getOplog(key, length))
|
|
1151
|
+
promises.push(this._getOplog(key, length, timeout ? { timeout } : null))
|
|
1129
1152
|
}
|
|
1130
1153
|
|
|
1131
1154
|
const ops = await Promise.all(promises)
|
|
1132
|
-
if (this.
|
|
1155
|
+
if (this.fastForwarding || this.fastForwardTo) return false
|
|
1133
1156
|
|
|
1134
1157
|
let best = null
|
|
1135
1158
|
let bestFlushes = -1
|
|
@@ -1144,10 +1167,6 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1144
1167
|
}
|
|
1145
1168
|
}
|
|
1146
1169
|
|
|
1147
|
-
if (best && force) {
|
|
1148
|
-
return this._rebootFromHead(best, null, { force: true })
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
1170
|
if (best === null || bestFlushes - this.system.flushes < MIN_FF_GAP) return false
|
|
1152
1171
|
|
|
1153
1172
|
const head = { key: best.key, length: best.length }
|
|
@@ -1158,24 +1177,30 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1158
1177
|
let trusted = null
|
|
1159
1178
|
try {
|
|
1160
1179
|
trusted = await this._handlers.onwakeup(head, view, this)
|
|
1161
|
-
if (!trusted || this.
|
|
1180
|
+
if (!trusted || this.fastForwarding || this.fastForwardTo) return false
|
|
1162
1181
|
} finally {
|
|
1163
1182
|
view.close()
|
|
1164
1183
|
}
|
|
1165
1184
|
|
|
1166
|
-
return this.
|
|
1185
|
+
return this._fastForwardFromHead(best, trusted, { wait, timeout })
|
|
1167
1186
|
}
|
|
1168
1187
|
|
|
1169
|
-
async
|
|
1188
|
+
async _fastForwardFromHead(head, trusted, { force = false, wait = true, timeout = 0 } = {}) {
|
|
1170
1189
|
const conservative = !force && this._conservativeFF
|
|
1190
|
+
const timeoutOpts = timeout ? { timeout } : null
|
|
1171
1191
|
|
|
1172
|
-
const oplog = await this._getOplog(head.key, head.length, {
|
|
1192
|
+
const oplog = await this._getOplog(head.key, head.length, {
|
|
1193
|
+
conservative,
|
|
1194
|
+
...timeoutOpts
|
|
1195
|
+
})
|
|
1173
1196
|
if (!oplog) return null
|
|
1174
1197
|
|
|
1175
|
-
const verified = trusted
|
|
1198
|
+
const verified = trusted
|
|
1199
|
+
? await this._getOplog(trusted.key, trusted.length, timeoutOpts)
|
|
1200
|
+
: oplog
|
|
1176
1201
|
if (!verified) return null
|
|
1177
1202
|
|
|
1178
|
-
// legacy nodes from non-indexers have no system info to
|
|
1203
|
+
// legacy nodes from non-indexers have no system info to fast-forward from
|
|
1179
1204
|
if (!oplog.op.views || !verified.op.views) return null
|
|
1180
1205
|
|
|
1181
1206
|
if (!force && verified.op.views.flushes - this.system.flushes < MIN_FF_GAP) {
|
|
@@ -1192,7 +1217,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1192
1217
|
|
|
1193
1218
|
const moved = await this._moveTo(batchToHead(verified.op.views.system), tip)
|
|
1194
1219
|
|
|
1195
|
-
if (moved && wait) return this.
|
|
1220
|
+
if (moved && wait) return this.ff.promise
|
|
1196
1221
|
|
|
1197
1222
|
return null
|
|
1198
1223
|
}
|
|
@@ -1201,46 +1226,46 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1201
1226
|
_initFromHead(head, tip) {
|
|
1202
1227
|
if (!head.length) {
|
|
1203
1228
|
// legacy fastForward boot
|
|
1204
|
-
return this.
|
|
1229
|
+
return this._runFastForward(new FastForward(this, head, tip))
|
|
1205
1230
|
}
|
|
1206
1231
|
|
|
1207
|
-
return this.
|
|
1232
|
+
return this._fastForwardFromHead(head, null, { force: true, wait: false })
|
|
1208
1233
|
}
|
|
1209
1234
|
|
|
1210
|
-
// head is a system head;
|
|
1235
|
+
// head is a system head; fast-forwards onto it, migrating in place if it's a legacy version
|
|
1211
1236
|
async _moveTo(systemHead, tip) {
|
|
1212
|
-
if (this.
|
|
1237
|
+
if (this.fastForwardTo !== null || this.fastForwarding !== null) return false
|
|
1213
1238
|
|
|
1214
|
-
if (await this.
|
|
1239
|
+
if (await this._runFastForward(new FastForward(this, systemHead, tip))) {
|
|
1215
1240
|
return true
|
|
1216
1241
|
}
|
|
1217
1242
|
|
|
1218
1243
|
return false
|
|
1219
1244
|
}
|
|
1220
1245
|
|
|
1221
|
-
async
|
|
1222
|
-
this.
|
|
1246
|
+
async _runFastForward(ff) {
|
|
1247
|
+
this.fastForwarding = ff
|
|
1223
1248
|
|
|
1224
|
-
const result = await
|
|
1225
|
-
await
|
|
1249
|
+
const result = await ff.run()
|
|
1250
|
+
await ff.close()
|
|
1226
1251
|
|
|
1227
|
-
if (this.
|
|
1252
|
+
if (this.fastForwarding === ff) this.fastForwarding = null
|
|
1228
1253
|
|
|
1229
1254
|
if (!result) return false
|
|
1230
1255
|
|
|
1231
|
-
this.
|
|
1232
|
-
this.
|
|
1256
|
+
this.fastForwardTo = result
|
|
1257
|
+
this.ff = rrp()
|
|
1233
1258
|
|
|
1234
1259
|
this.bumpSoon()
|
|
1235
1260
|
|
|
1236
1261
|
return true
|
|
1237
1262
|
}
|
|
1238
1263
|
|
|
1239
|
-
async
|
|
1264
|
+
async _applyFastForward() {
|
|
1240
1265
|
const changes = this._hasUpdate ? new UpdateChanges(this) : null
|
|
1241
1266
|
if (changes) changes.track()
|
|
1242
1267
|
|
|
1243
|
-
const { head, tip, migrate } = this.
|
|
1268
|
+
const { head, tip, migrate } = this.fastForwardTo
|
|
1244
1269
|
|
|
1245
1270
|
const from = this.system.bee.head()
|
|
1246
1271
|
const to = head
|
|
@@ -1248,7 +1273,7 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1248
1273
|
this.system.bee.move(head)
|
|
1249
1274
|
await this.system.reset()
|
|
1250
1275
|
|
|
1251
|
-
this.system.shared = this.
|
|
1276
|
+
this.system.shared = this.fastForwardTo.shared || {
|
|
1252
1277
|
flushes: -1,
|
|
1253
1278
|
view: EMPTY_HEAD,
|
|
1254
1279
|
system: EMPTY_HEAD
|
|
@@ -1265,15 +1290,16 @@ module.exports = class Autobee extends ReadyResource {
|
|
|
1265
1290
|
this._workingBee.move(this.system.view)
|
|
1266
1291
|
}
|
|
1267
1292
|
|
|
1268
|
-
this.
|
|
1293
|
+
this.fastForwardTo = null
|
|
1269
1294
|
await this.writers.refresh()
|
|
1270
1295
|
|
|
1271
1296
|
await this._storeBoot()
|
|
1272
1297
|
|
|
1273
1298
|
await this._update(changes)
|
|
1274
1299
|
|
|
1300
|
+
this.stats.fastForwards++
|
|
1275
1301
|
this.emit('move-to', to, from)
|
|
1276
|
-
this.
|
|
1302
|
+
this.ff.resolve({ to, from })
|
|
1277
1303
|
|
|
1278
1304
|
// tip is null during boot
|
|
1279
1305
|
if (!tip) return
|
package/lib/boot.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const safetyCatch = require('safety-catch')
|
|
2
2
|
const b4a = require('b4a')
|
|
3
|
+
const Hypercore = require('hypercore')
|
|
3
4
|
const c = require('compact-encoding')
|
|
4
5
|
const crypto = require('hypercore-crypto')
|
|
5
6
|
|
|
@@ -19,12 +20,18 @@ const [NS_SIGNER_NAMESPACE] = crypto.namespace('autobase', 1)
|
|
|
19
20
|
const EMPTY = b4a.alloc(0)
|
|
20
21
|
const DEFAULT_OP_TIMEOUT = 5_000
|
|
21
22
|
|
|
22
|
-
module.exports = class
|
|
23
|
+
module.exports = class FastForward {
|
|
23
24
|
constructor(auto, head, tip, { timeout = DEFAULT_OP_TIMEOUT } = {}) {
|
|
24
25
|
this.auto = auto
|
|
26
|
+
|
|
27
|
+
this.activeRequests = []
|
|
28
|
+
|
|
29
|
+
// 'reboot' is a storage namespace: renaming it would derive a different
|
|
30
|
+
// scratch core on disk, so it stays as is
|
|
25
31
|
this.system = new System(auto.store.namespace('reboot'), null, {
|
|
26
32
|
getEncryptionProvider: this.auto.getSystemEncryption,
|
|
27
|
-
encrypted: this.encrypted
|
|
33
|
+
encrypted: this.encrypted,
|
|
34
|
+
activeRequests: this.activeRequests
|
|
28
35
|
})
|
|
29
36
|
|
|
30
37
|
// length -1 marks an uninitialised head: we know the key but not the length
|
|
@@ -37,6 +44,8 @@ module.exports = class Reboot {
|
|
|
37
44
|
this.cores = []
|
|
38
45
|
}
|
|
39
46
|
|
|
47
|
+
static DEFAULT_TIMEOUT = DEFAULT_OP_TIMEOUT
|
|
48
|
+
|
|
40
49
|
async run() {
|
|
41
50
|
try {
|
|
42
51
|
if (!this.running) this.running = this._run()
|
|
@@ -77,7 +86,12 @@ module.exports = class Reboot {
|
|
|
77
86
|
const view = this.auto.store.get({ key: this.system.view.key, active: true })
|
|
78
87
|
this.cores.push(view)
|
|
79
88
|
|
|
80
|
-
promises.push(
|
|
89
|
+
promises.push(
|
|
90
|
+
view.get(this.system.view.length - 1, {
|
|
91
|
+
timeout: this.timeout,
|
|
92
|
+
activeRequests: this.activeRequests
|
|
93
|
+
})
|
|
94
|
+
)
|
|
81
95
|
|
|
82
96
|
for (const head of this.system.heads) {
|
|
83
97
|
promises.push(this.system.get(head.key, { timeout: this.timeout }))
|
|
@@ -118,7 +132,12 @@ module.exports = class Reboot {
|
|
|
118
132
|
await core.setEncryption(this.auto.getSystemEncryption())
|
|
119
133
|
|
|
120
134
|
// Decode hyperbee block using hyperbee2 compat
|
|
121
|
-
const node = decodeBlock(
|
|
135
|
+
const node = decodeBlock(
|
|
136
|
+
await core.get(this.head.length - 1, {
|
|
137
|
+
timeout: this.timeout,
|
|
138
|
+
activeRequests: this.activeRequests
|
|
139
|
+
})
|
|
140
|
+
)
|
|
122
141
|
assert(node.keys.length > 0, 'bad system block')
|
|
123
142
|
|
|
124
143
|
// Decode system info from hyperbee block using autobee compat
|
|
@@ -163,7 +182,12 @@ module.exports = class Reboot {
|
|
|
163
182
|
await core.ready()
|
|
164
183
|
|
|
165
184
|
try {
|
|
166
|
-
if (!core.manifest)
|
|
185
|
+
if (!core.manifest) {
|
|
186
|
+
await core.get(length - 1, {
|
|
187
|
+
timeout: this.timeout,
|
|
188
|
+
activeRequests: this.activeRequests
|
|
189
|
+
})
|
|
190
|
+
}
|
|
167
191
|
} catch {
|
|
168
192
|
return null
|
|
169
193
|
}
|
|
@@ -191,8 +215,14 @@ module.exports = class Reboot {
|
|
|
191
215
|
}
|
|
192
216
|
}
|
|
193
217
|
|
|
218
|
+
// cancels every read in flight and fails any that come after
|
|
219
|
+
clearRequests(err = null) {
|
|
220
|
+
Hypercore.destroyRequests(this.activeRequests, err)
|
|
221
|
+
}
|
|
222
|
+
|
|
194
223
|
async close() {
|
|
195
224
|
this.destroyed = true
|
|
225
|
+
this.clearRequests()
|
|
196
226
|
if (this.system) await this.system.close()
|
|
197
227
|
for (const core of this.cores) await core.close()
|
|
198
228
|
}
|