autobee 2.0.0-rc.17 → 2.0.0-rc.18

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
@@ -235,6 +235,8 @@ module.exports = class Autobee extends ReadyResource {
235
235
  await this._draining
236
236
  }
237
237
 
238
+ if (this._updating) await this._updating
239
+
238
240
  // let in-flight writer adds finish
239
241
  try {
240
242
  await this._bootingAll
@@ -263,25 +265,6 @@ module.exports = class Autobee extends ReadyResource {
263
265
  if (this.fastForwarding) this.fastForwarding.clearRequests(closingError)
264
266
  }
265
267
 
266
- async changesFrom({ system } = {}) {
267
- const info = await this.system.getInfo()
268
-
269
- const current = {
270
- flushes: info ? info.flushes : 0,
271
- system: this.system.bee.head(),
272
- view: info ? info.view : EMPTY_HEAD
273
- }
274
-
275
- // no common ancestor (null) -> flushes -1 never matches, forcing a full diff
276
- const shared = (await this.system.commonAncestor(system || EMPTY_HEAD)) || {
277
- flushes: -1,
278
- system: EMPTY_HEAD,
279
- view: EMPTY_HEAD
280
- }
281
-
282
- return UpdateChanges.from(shared, current)
283
- }
284
-
285
268
  replicate(...args) {
286
269
  const stream = this.store.replicate(...args)
287
270
  this._wakeup.addStream(stream)
@@ -529,9 +512,6 @@ module.exports = class Autobee extends ReadyResource {
529
512
  else if (head) await this._bootFromHead(head, bootCondition)
530
513
  }
531
514
 
532
- // tracked across drain iteration
533
- this.system.shared = null
534
-
535
515
  this._ackRequired = false
536
516
 
537
517
  const changes = this._hasUpdate ? new UpdateChanges(this) : null
@@ -566,6 +546,8 @@ module.exports = class Autobee extends ReadyResource {
566
546
  }
567
547
 
568
548
  await this._flushLocal()
549
+
550
+ if (!this._interrupting) await this.writers.refresh()
569
551
  } finally {
570
552
  if (this.bumping === 1) this.bumping = 0
571
553
  else this.bumping = 1
@@ -575,16 +557,15 @@ module.exports = class Autobee extends ReadyResource {
575
557
  this._draining = null
576
558
  if (this._interrupting) return
577
559
 
578
- if (this._needsUpdate) {
579
- const updating = rrp()
580
- this._updating = updating.promise
560
+ const updating = rrp()
561
+ this._updating = updating.promise
581
562
 
582
- try {
583
- await this._update(changes)
584
- } finally {
585
- this._updating = null
586
- updating.resolve()
587
- }
563
+ try {
564
+ if (this._needsUpdate) await this._update(changes)
565
+ await this._storeBoot()
566
+ } finally {
567
+ this._updating = null
568
+ updating.resolve()
588
569
  }
589
570
  }
590
571
 
@@ -617,18 +598,28 @@ module.exports = class Autobee extends ReadyResource {
617
598
  }
618
599
  }
619
600
 
620
- async _flushWakeup() {
601
+ // wakeup hints keep arriving asynchronously
602
+ async _applyWakeupHints() {
621
603
  const hints = this._wakeup.flush()
622
- if (!hints.size) return
604
+ if (!hints.size) return hints
623
605
 
624
606
  this.previousDrain = Date.now()
625
607
 
626
608
  for (const [hex, length] of hints) {
627
609
  const key = b4a.from(hex, 'hex')
628
- if (this.writers.has(hex)) continue
610
+ // wakeup() itself no-ops the add for an already-active writer, but still
611
+ // needs to run so it can hint() the announced length - skipping it here
612
+ // for active writers left gc with no hint to protect them
629
613
  await this.writers.wakeup(key, length)
630
614
  }
631
615
 
616
+ return hints
617
+ }
618
+
619
+ async _flushWakeup() {
620
+ const hints = await this._applyWakeupHints()
621
+ if (!hints.size) return
622
+
632
623
  // a scheduled fast-forward is applied by the drain before we look again
633
624
  if (this.fastForwardTo !== null || this.fastForwarding !== null) return
634
625
  if (this._interrupting || this.closing || this.bootFrom) return
@@ -952,7 +943,6 @@ module.exports = class Autobee extends ReadyResource {
952
943
  async _optimisticBatch(batch) {
953
944
  const rollbackSystem = this.system.bee.head()
954
945
  const rollbackView = this._workingBee.head()
955
- const rollbackShared = this.system.shared
956
946
  const rollbackAttestations = this.writers.attestations.length
957
947
 
958
948
  const t = await this.prepareBatch(batch)
@@ -975,7 +965,6 @@ module.exports = class Autobee extends ReadyResource {
975
965
  this._workingBee.move(rollbackView)
976
966
  this.system.bee.move(rollbackSystem)
977
967
  await this.system.reset()
978
- this.system.shared = rollbackShared
979
968
  // don't attest grants that were just undone
980
969
  this.writers.attestations.length = rollbackAttestations
981
970
  return false
@@ -1064,8 +1053,6 @@ module.exports = class Autobee extends ReadyResource {
1064
1053
 
1065
1054
  this.writers.attest(witnessed)
1066
1055
 
1067
- await this._storeBoot()
1068
-
1069
1056
  for (const { key, added, isAnchor } of changed) {
1070
1057
  if (isAnchor) {
1071
1058
  // no Writer/ActiveWriters tracking, but still wake anything linked to it
@@ -1327,12 +1314,6 @@ module.exports = class Autobee extends ReadyResource {
1327
1314
  this.system.bee.move(head)
1328
1315
  await this.system.reset()
1329
1316
 
1330
- this.system.shared = this.fastForwardTo.shared || {
1331
- flushes: -1,
1332
- view: EMPTY_HEAD,
1333
- system: EMPTY_HEAD
1334
- }
1335
-
1336
1317
  // migrate is set when fast-forwarding from a legacy head
1337
1318
  if (migrate) {
1338
1319
  const view = (await this._handlers.migrate(migrate)) || EMPTY_HEAD
@@ -1345,14 +1326,16 @@ module.exports = class Autobee extends ReadyResource {
1345
1326
  }
1346
1327
 
1347
1328
  this.fastForwardTo = null
1329
+
1330
+ // process any wakeup while fast-forward itself was in flight
1331
+ await this._applyWakeupHints()
1348
1332
  await this.writers.refresh()
1349
1333
 
1350
1334
  // we moved, so ask our peers to tell us their heads again
1351
1335
  this._requestWakeup()
1352
1336
 
1353
- await this._storeBoot()
1354
-
1355
1337
  await this._update(changes)
1338
+ await this._storeBoot()
1356
1339
 
1357
1340
  this.stats.fastForwards++
1358
1341
  this.emit('move-to', to, from)
@@ -261,23 +261,12 @@ module.exports = class FastForward {
261
261
  await Promise.all(promises)
262
262
  if (this.destroyed) return null
263
263
 
264
- const shared = await this._commonAncestor()
265
264
  if (this.destroyed) return null
266
265
 
267
266
  return {
268
267
  head: this.head,
269
268
  tip: this.tip,
270
- migrate: null,
271
- shared
272
- }
273
- }
274
-
275
- async _commonAncestor() {
276
- try {
277
- return await this.auto.system.commonAncestor(this.head)
278
- } catch (err) {
279
- safetyCatch(err)
280
- return null
269
+ migrate: null
281
270
  }
282
271
  }
283
272
 
package/lib/system.js CHANGED
@@ -46,7 +46,6 @@ module.exports = class Systembee {
46
46
  this.indexers = null // legacy
47
47
  this.updates = new Map()
48
48
  this.writers = new Map()
49
- this.shared = null
50
49
  this.encrypted = opts.encrypted === true
51
50
  }
52
51
 
@@ -115,60 +114,6 @@ module.exports = class Systembee {
115
114
  }
116
115
  }
117
116
 
118
- async commonAncestor(head) {
119
- const snap = this.bee.checkout(head)
120
-
121
- const ca = infoIterator(this.bee)
122
- const cb = infoIterator(snap)
123
-
124
- let l = await next(ca)
125
- let r = await next(cb)
126
-
127
- while (l && r) {
128
- if (l.legacy && r.legacy) {
129
- // legacy nodes just compare head
130
- if (sameHead(l.node.head, r.node.head)) {
131
- return {
132
- flushes: l.info.flushes,
133
- system: l.node.head,
134
- view: l.info.view || EMPTY_HEAD
135
- }
136
- }
137
-
138
- // different legacy cores never converge - genuinely unrelated history
139
- if (!b4a.equals(l.node.head.key, r.node.head.key)) return null
140
-
141
- if (l.node.head.length >= r.node.head.length) l = await next(ca)
142
- else r = await next(cb)
143
- continue
144
- }
145
-
146
- if (l.legacy !== r.legacy) {
147
- // current-format history sits above the legacy band: descend it
148
- if (!l.legacy) l = await next(ca)
149
- else r = await next(cb)
150
- continue
151
- }
152
-
153
- if (l.info.flushes === r.info.flushes && sameHead(l.info.view, r.info.view)) {
154
- return {
155
- flushes: l.info.flushes,
156
- system: l.node.head,
157
- view: l.info.view || EMPTY_HEAD
158
- }
159
- }
160
-
161
- const lf = l.info.flushes
162
- const rf = r.info.flushes
163
-
164
- if (lf >= rf) l = await next(ca)
165
- if (rf >= lf) r = await next(cb)
166
- }
167
-
168
- // no common ancestor
169
- return null
170
- }
171
-
172
117
  async reset({ timeout } = {}) {
173
118
  const info = await this.getInfo({ timeout })
174
119
 
@@ -178,7 +123,6 @@ module.exports = class Systembee {
178
123
  this.timestamp = info ? info.timestamp : 0
179
124
  this.flushes = info ? info.flushes : 0
180
125
  this.indexers = info ? info.indexers : null
181
- this.shared = null
182
126
  this.updates.clear()
183
127
  this.writers.clear()
184
128
 
@@ -200,16 +144,8 @@ module.exports = class Systembee {
200
144
  async undo(head) {
201
145
  this.bee.move(head)
202
146
 
203
- const shared = this.shared
204
147
  await this.reset()
205
148
 
206
- // deepest rewind since last mark - everything at or below it is untouched
207
- if (shared === null || this.flushes < shared.flushes) {
208
- this.shared = { flushes: this.flushes, view: this.view || EMPTY_HEAD, system: head }
209
- } else if (shared) {
210
- this.shared = shared
211
- }
212
-
213
149
  return this.view || EMPTY_HEAD
214
150
  }
215
151
 
@@ -514,41 +450,3 @@ function getInfo(data) {
514
450
 
515
451
  return null
516
452
  }
517
-
518
- function sameHead(a, b) {
519
- if (!a || !b || a.length !== b.length) return false
520
- if (!a.key || !b.key) return a.key === b.key
521
- return b4a.equals(a.key, b.key)
522
- }
523
-
524
- async function* infoIterator(bee) {
525
- try {
526
- for await (const node of bee.createChangesStream()) {
527
- const legacy = topo.isLegacyNode(node)
528
- const info = legacy ? getLegacyInfo(node) : getInfo(node)
529
- if (info) yield { node, info, legacy }
530
- }
531
- } catch {
532
- // fall through
533
- }
534
- }
535
-
536
- async function next(it) {
537
- const res = await it.next()
538
- return res.done ? null : res.value
539
- }
540
-
541
- function getLegacyInfo(data) {
542
- for (const { keys } of data.batch) {
543
- for (const k of keys) {
544
- if (b4a.equals(k.key, INFO_LEGACY_KEY)) {
545
- try {
546
- return encoding.decodeSystemInfo(k.value)
547
- } catch {
548
- return null
549
- }
550
- }
551
- }
552
- }
553
- return null
554
- }
package/lib/topo.js CHANGED
@@ -6,6 +6,8 @@ const asserts = require('./asserts.js')
6
6
  const EMPTY_HEAD = { length: 0, key: null }
7
7
  const INFO_LEGACY_KEY = b4a.concat([b4a.from([0, 0]), b4a.from('info')])
8
8
 
9
+ const DEFAULT_OP_TIMEOUT = 5000
10
+
9
11
  exports.sort = sort
10
12
  exports.rollback = rollback
11
13
  exports.replay = replay
@@ -229,6 +231,8 @@ function cmp(a, b) {
229
231
  }
230
232
 
231
233
  async function inflate(auto, reorder, opts = null) {
234
+ const readOpts = opts === null ? { timeout: DEFAULT_OP_TIMEOUT } : opts
235
+
232
236
  const promises = []
233
237
  const sessions = new Map()
234
238
 
@@ -257,7 +261,7 @@ async function inflate(auto, reorder, opts = null) {
257
261
  sessions.set(id, core)
258
262
  }
259
263
 
260
- promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i, opts))
264
+ promises.push(getOplogBatch(ref.undo, core, ref.oplog.length, weight, i, readOpts))
261
265
  }
262
266
 
263
267
  try {
package/lib/updates.js CHANGED
@@ -1,5 +1,3 @@
1
- const b4a = require('b4a')
2
-
3
1
  module.exports = class UpdateChanges {
4
2
  constructor(auto) {
5
3
  this.auto = auto
@@ -21,57 +19,22 @@ module.exports = class UpdateChanges {
21
19
  flushes: this.auto.system.flushes,
22
20
  from: this.tracking[k].head(),
23
21
  to: null,
24
- shared: null
22
+ incremental: false
25
23
  })
26
24
  }
27
25
  }
28
26
 
29
27
  finalise() {
30
- const floor = this.auto.system.shared
31
-
32
28
  // flush count at `to` - safe to read live, state is frozen for the hook
33
29
  const flushes = this.auto.system.flushes
34
30
 
35
31
  for (const k in this.tracking) {
36
32
  const tracked = this.byName.get(k)
37
- const startFlushes = tracked.flushes
38
33
 
39
34
  tracked.to = this.tracking[k].head()
40
35
  tracked.flushes = flushes
41
-
42
- if (floor && floor.flushes < startFlushes) {
43
- tracked.shared = k === 'view' ? floor.view : floor.system
44
- } else {
45
- tracked.shared = tracked.from
46
- }
47
36
  }
48
37
 
49
38
  return this.byName
50
39
  }
51
-
52
- static from(shared, current) {
53
- if (
54
- shared.flushes === current.flushes &&
55
- shared.system.length === current.system.length &&
56
- (shared.system.key === current.system.key ||
57
- (shared.system.key &&
58
- current.system.key &&
59
- b4a.equals(shared.system.key, current.system.key)))
60
- ) {
61
- return null
62
- }
63
-
64
- const byName = new Map()
65
- const flushes = current.flushes
66
-
67
- byName.set('view', { from: shared.view, shared: shared.view, to: current.view, flushes })
68
- byName.set('system', {
69
- from: shared.system,
70
- shared: shared.system,
71
- to: current.system,
72
- flushes
73
- })
74
-
75
- return byName
76
- }
77
40
  }
package/lib/writers.js CHANGED
@@ -62,13 +62,15 @@ class ActiveWriters {
62
62
  const info = await this.auto.system.get(key, { unflushed: true })
63
63
  if (info && info.isAnchor) return
64
64
  w = await this.add(key)
65
+ if (!w) return
65
66
  }
66
67
 
68
+ // remember the announced length so gc doesn't drop a writer we haven't caught up on
69
+ w.hint(length)
70
+
67
71
  // prefetch the oplog head for the fast-forward that runs right after us
68
72
  if (w && length > 0) w.core.get(length - 1).catch(noop)
69
73
 
70
- // TODO: signal that we've seen length on w for gc
71
-
72
74
  this.bump()
73
75
  }
74
76
 
@@ -215,6 +217,7 @@ class ActiveWriters {
215
217
  const id = b4a.toString(bootstrap.key, 'hex')
216
218
  let w = this.active.get(id)
217
219
  if (w) {
220
+ w.isBootstrap = true
218
221
  w.couple() // couple if we weren't ready first time
219
222
  return
220
223
  }
@@ -223,6 +226,7 @@ class ActiveWriters {
223
226
  session.setEncryption(this.auto._getEncryptionProvider())
224
227
 
225
228
  w = new Writer(this, this.auto.system, false, session, id)
229
+ w.isBootstrap = true // the root of the autobase, never gc'd
226
230
  w.attach()
227
231
  w.couple()
228
232
  }
@@ -253,6 +257,7 @@ class Writer {
253
257
  this.appendLength = this.core.length + 1
254
258
  this.waiting = null
255
259
  this.download = null
260
+ this.hintedLength = 0
256
261
 
257
262
  this.isPending = false
258
263
  this.isClosed = false
@@ -261,6 +266,7 @@ class Writer {
261
266
  this.isRemoved = false
262
267
  this.isCoupled = false
263
268
  this.isFrozen = false
269
+ this.isBootstrap = false
264
270
 
265
271
  this._onchangeBound = this._onchange.bind(this)
266
272
 
@@ -315,6 +321,11 @@ class Writer {
315
321
  }
316
322
  }
317
323
 
324
+ // a peer announced this length for us, so we're not caught up until we've seen it
325
+ hint(length) {
326
+ if (length > this.hintedLength) this.hintedLength = length
327
+ }
328
+
318
329
  _onchange() {
319
330
  if (this.waiting === null) this.bump()
320
331
  }
@@ -352,10 +363,10 @@ class Writer {
352
363
 
353
364
  const length = await this.update()
354
365
 
355
- // don't drop any local nodes left in buffer
356
- if (length < this.core.length || this.appending) {
366
+ // don't drop any local nodes left in buffer, or work a peer told us about
367
+ if (length < this.core.length || length < this.hintedLength || this.appending) {
357
368
  this.addPending()
358
- } else if (this !== this.writers.localWriter && !this.isIndexer) {
369
+ } else if (this !== this.writers.localWriter && !this.isIndexer && !this.isBootstrap) {
359
370
  await this.detachAndClose()
360
371
  }
361
372
  }
@@ -375,6 +386,7 @@ class Writer {
375
386
 
376
387
  detachAndClose() {
377
388
  this.detach()
389
+ this.decouple()
378
390
  return this.close()
379
391
  }
380
392
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autobee",
3
- "version": "2.0.0-rc.17",
3
+ "version": "2.0.0-rc.18",
4
4
  "description": "Bee based autobase",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Holepunch Inc.",