autobee 2.2.0 → 2.2.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.
Files changed (3) hide show
  1. package/index.js +25 -1
  2. package/lib/writers.js +64 -33
  3. package/package.json +2 -1
package/index.js CHANGED
@@ -344,6 +344,29 @@ module.exports = class Autobee extends ReadyResource {
344
344
  async _close() {
345
345
  this._interrupting = true
346
346
 
347
+ // the local core holds the exclusive lock but the local writer closes it
348
+ // early in the teardown, so hand the lock to a detached session that
349
+ // outlives the teardown and only release it once everything is down
350
+ let lock = null
351
+
352
+ if (this.local.exclusive) {
353
+ lock = this.store.session()
354
+ const local = lock.get({ key: this.local.key, active: false })
355
+ await local.ready()
356
+
357
+ // transfer exclusiveness, the detached session now unlocks on close
358
+ local.exclusive = true
359
+ this.local.exclusive = false
360
+ }
361
+
362
+ try {
363
+ await this._teardown()
364
+ } finally {
365
+ if (lock) await lock.close()
366
+ }
367
+ }
368
+
369
+ async _teardown() {
347
370
  try {
348
371
  await ApplyView.close(this.view, this)
349
372
  } catch (err) {
@@ -351,7 +374,6 @@ module.exports = class Autobee extends ReadyResource {
351
374
  }
352
375
 
353
376
  if (this.writers) await this.writers.close()
354
- await this.local.close()
355
377
  await this.system.close()
356
378
  await this._wakeup.close()
357
379
  if (this.bootstrap) await this.bootstrap.close()
@@ -366,6 +388,8 @@ module.exports = class Autobee extends ReadyResource {
366
388
  // don't rely on the store teardown to stop the notify watcher
367
389
  if (this._notifyHandler) this._notifyHandler.destroy()
368
390
 
391
+ await this.local.close()
392
+
369
393
  // rugpull the rest
370
394
  await this.store.close()
371
395
 
package/lib/writers.js CHANGED
@@ -4,6 +4,7 @@ const Triggers = require('./triggers.js')
4
4
  const { LEGACY_OPLOG_VERSION } = require('./constants.js')
5
5
  const { resolveWeight } = require('./witness.js')
6
6
  const { WriterEncryption } = require('autobee-encryption')
7
+ const ReadWriteLock = require('read-write-mutexify')
7
8
 
8
9
  const WRITER_PREFETCH = 10
9
10
 
@@ -25,6 +26,7 @@ class ActiveWriters {
25
26
 
26
27
  this._pendingBuffer = new Map()
27
28
  this._adding = new Map()
29
+ this._lock = new ReadWriteLock()
28
30
 
29
31
  this.localWriter = new Writer(this, auto.system, true, local, b4a.toString(local.key, 'hex'))
30
32
  this.localWriter.attach()
@@ -89,14 +91,27 @@ class ActiveWriters {
89
91
  }
90
92
 
91
93
  async rotateLocalWriter(core) {
92
- this.clearLocal()
93
- await this.localWriter.detachAndClose()
94
+ await this._lock.write.lock()
94
95
 
95
- this.localWriter = new Writer(this, this.auto.system, true, core, b4a.toString(core.key, 'hex'))
96
- this.localWriter.addPending()
97
- this.auto.emit('writer', this.localWriter)
96
+ try {
97
+ this.clearLocal()
98
+ await this.localWriter.detachAndClose()
99
+
100
+ const id = b4a.toString(core.key, 'hex')
101
+
102
+ // a peer may already have announced this core (wakeup hint / link), in which
103
+ // case an external writer tracks it - drop it so only one writer owns the key
104
+ const existing = this.active.get(id)
105
+ if (existing) await existing.detachAndClose()
98
106
 
99
- await this.updateLocalState()
107
+ this.localWriter = new Writer(this, this.auto.system, true, core, id)
108
+ // register in active like the constructor does, so add() finds it
109
+ this.localWriter.attach()
110
+
111
+ await this.updateLocalState()
112
+ } finally {
113
+ this._lock.write.unlock()
114
+ }
100
115
  }
101
116
 
102
117
  clearLocal() {
@@ -178,7 +193,7 @@ class ActiveWriters {
178
193
  return !!this.active.get(id)
179
194
  }
180
195
 
181
- add(key) {
196
+ async add(key) {
182
197
  const id = b4a.toString(key, 'hex')
183
198
 
184
199
  let adding = this._adding.get(id)
@@ -187,44 +202,60 @@ class ActiveWriters {
187
202
  adding = this._add(key, id)
188
203
  this._adding.set(id, adding)
189
204
 
190
- return adding.finally(() => {
205
+ try {
206
+ return await adding
207
+ } finally {
191
208
  this._adding.delete(id)
192
- })
209
+ }
193
210
  }
194
211
 
195
- // TODO: correct fix is writer as ReadyResource
196
212
  async _add(key, id) {
197
- const local = b4a.equals(key, this.auto.local.key)
198
- if (local) await this.updateLocalState()
213
+ await this._lock.read.lock()
199
214
 
200
- let w = this.active.get(id)
201
- if (w) return w
215
+ try {
216
+ if (this.auto._interrupting) return null
217
+
218
+ // judged against the writer we own, not auto.local - that flips before the
219
+ // rotation takes the write lock. an add that beats the rotation onto its
220
+ // key just makes an external writer, which the rotation then evicts
221
+ if (b4a.equals(key, this.localWriter.core.key)) {
222
+ await this.updateLocalState()
223
+ if (this.auto._interrupting) return null
224
+ // never track the local core with a second writer - reattach it if it
225
+ // was detached (i.e. removed earlier and now re-added)
226
+ this.localWriter.attach()
227
+ return this.localWriter
228
+ }
202
229
 
203
- // no new sessions once teardown started
204
- if (this.auto._interrupting) return null
230
+ let w = this.active.get(id)
231
+ if (w) return w
205
232
 
206
- const encryption = this.auto.encryptionKey ? new WriterEncryption(this.auto) : null
233
+ const local = b4a.equals(key, this.auto.local.key)
234
+ const encryption = this.auto.encryptionKey ? new WriterEncryption(this.auto) : null
207
235
 
208
- const core = this.auto.store.get({
209
- active: false,
210
- key,
211
- encryption,
212
- group: local || !this.auto.wakeupCapability ? null : this.auto.wakeupCapability.discoveryKey
213
- })
236
+ const core = this.auto.store.get({
237
+ active: false,
238
+ key,
239
+ encryption,
240
+ group: local || !this.auto.wakeupCapability ? null : this.auto.wakeupCapability.discoveryKey
241
+ })
214
242
 
215
- await core.ready()
216
- await core.setUserData('referrer', this.auto.key)
243
+ await core.ready()
244
+ await core.setUserData('referrer', this.auto.key)
217
245
 
218
- // teardown may have started underneath us
219
- if (this.auto._interrupting) {
220
- await core.close()
221
- return null
222
- }
246
+ // teardown may have started underneath us
247
+ if (this.auto._interrupting) {
248
+ await core.close()
249
+ return null
250
+ }
223
251
 
224
- w = new Writer(this, this.auto.system, false, core, id)
225
- w.attach()
252
+ w = new Writer(this, this.auto.system, false, core, id)
253
+ w.attach()
226
254
 
227
- return w
255
+ return w
256
+ } finally {
257
+ this._lock.read.unlock()
258
+ }
228
259
  }
229
260
 
230
261
  async remove(key) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autobee",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "Bee based autobase",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Holepunch Inc.",
@@ -31,6 +31,7 @@
31
31
  "hypercore": "^11.33.2",
32
32
  "hypercore-id-encoding": "^1.3.0",
33
33
  "hyperschema": "^1.20.1",
34
+ "read-write-mutexify": "^2.1.0",
34
35
  "ready-guard": "^1.0.0",
35
36
  "ready-resource": "^1.2.0",
36
37
  "resolve-reject-promise": "^1.1.0",