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 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 Reboot = require('./lib/reboot.js')
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.reboot = null
88
- this.rebooting = null
89
- this.rebootTo = null
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
- if (this.rebootTo !== null) {
526
- await this._applyReboot()
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 reboot
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 rebootFromHeads(hints, { force = false } = {}) {
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.rebooting || this.rebootTo || this.bootFrom) {
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.rebooting || this.rebootTo) return false
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.rebooting || this.rebootTo) return false
1180
+ if (!trusted || this.fastForwarding || this.fastForwardTo) return false
1162
1181
  } finally {
1163
1182
  view.close()
1164
1183
  }
1165
1184
 
1166
- return this._rebootFromHead(best, trusted)
1185
+ return this._fastForwardFromHead(best, trusted, { wait, timeout })
1167
1186
  }
1168
1187
 
1169
- async _rebootFromHead(head, trusted, { force = false, wait = true } = {}) {
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, { conservative })
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 ? await this._getOplog(trusted.key, trusted.length) : oplog
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 reboot from
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.reboot.promise
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._runReboot(new Reboot(this, head, tip))
1229
+ return this._runFastForward(new FastForward(this, head, tip))
1205
1230
  }
1206
1231
 
1207
- return this._rebootFromHead(head, null, { force: true, wait: false })
1232
+ return this._fastForwardFromHead(head, null, { force: true, wait: false })
1208
1233
  }
1209
1234
 
1210
- // head is a system head; reboots onto it, migrating in place if it's a legacy version
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.rebootTo !== null || this.rebooting !== null) return false
1237
+ if (this.fastForwardTo !== null || this.fastForwarding !== null) return false
1213
1238
 
1214
- if (await this._runReboot(new Reboot(this, systemHead, tip))) {
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 _runReboot(reboot) {
1222
- this.rebooting = reboot
1246
+ async _runFastForward(ff) {
1247
+ this.fastForwarding = ff
1223
1248
 
1224
- const result = await reboot.run()
1225
- await reboot.close()
1249
+ const result = await ff.run()
1250
+ await ff.close()
1226
1251
 
1227
- if (this.rebooting === reboot) this.rebooting = null
1252
+ if (this.fastForwarding === ff) this.fastForwarding = null
1228
1253
 
1229
1254
  if (!result) return false
1230
1255
 
1231
- this.rebootTo = result
1232
- this.reboot = rrp()
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 _applyReboot() {
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.rebootTo
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.rebootTo.shared || {
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.rebootTo = null
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.reboot.resolve({ to, from })
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
@@ -242,6 +242,7 @@ async function getCatchupHeads(core) {
242
242
  if (entry.key[0] !== 0x01) continue
243
243
 
244
244
  const { key, length } = encoding.decodeSystemWriter(entry.key, entry.value)
245
+ if (!length) continue
245
246
  nodes.push({ key, length })
246
247
  }
247
248
 
@@ -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 Reboot {
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(view.get(this.system.view.length - 1, { timeout: this.timeout }))
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(await core.get(this.head.length - 1, { timeout: this.timeout }))
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) await core.get(length - 1, { timeout: this.timeout })
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autobee",
3
- "version": "2.0.0-rc.13",
3
+ "version": "2.0.0-rc.15",
4
4
  "description": "Bee based autobase",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Holepunch Inc.",