hypercore 10.37.1 → 10.37.3

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
@@ -249,12 +249,16 @@ module.exports = class Hypercore extends EventEmitter {
249
249
  }
250
250
 
251
251
  if (this.opened) ensureEncryption(s, opts)
252
-
253
- this.sessions.push(s)
252
+ this._addSession(s)
254
253
 
255
254
  return s
256
255
  }
257
256
 
257
+ _addSession (s) {
258
+ this.sessions.push(s)
259
+ if (this.core) this.core.active++
260
+ }
261
+
258
262
  async setEncryptionKey (encryptionKey, opts) {
259
263
  if (!this.opened) await this.opening
260
264
  this.encryption = encryptionKey ? new BlockEncryption(encryptionKey, this.key, { compat: this.core.compat, ...opts }) : null
@@ -289,8 +293,8 @@ module.exports = class Hypercore extends EventEmitter {
289
293
 
290
294
  for (const s of sessions) {
291
295
  s.sessions = from.sessions
292
- s.sessions.push(s)
293
296
  s._passCapabilities(from)
297
+ s._addSession(s)
294
298
  }
295
299
 
296
300
  this.storage = from.storage
@@ -307,7 +311,9 @@ module.exports = class Hypercore extends EventEmitter {
307
311
  async _openSession (key, storage, opts) {
308
312
  const isFirst = !opts._opening
309
313
 
310
- if (!isFirst) await opts._opening
314
+ if (!isFirst) {
315
+ await opts._opening
316
+ }
311
317
  if (opts.preload) opts = { ...opts, ...(await this._retryPreload(opts.preload)) }
312
318
  if (this.cache === null && opts.cache) this.cache = createCache(opts.cache)
313
319
 
@@ -346,7 +352,7 @@ module.exports = class Hypercore extends EventEmitter {
346
352
  // It's required so that corestore can load a name from userData before 'ready' is emitted.
347
353
  if (opts._preready) await opts._preready(this)
348
354
 
349
- this.replicator.updateActivity(this._active ? 1 : 0, this)
355
+ this.replicator.updateActivity(this._active ? 1 : 0)
350
356
 
351
357
  this.opened = true
352
358
  this.emit('ready')
@@ -373,6 +379,7 @@ module.exports = class Hypercore extends EventEmitter {
373
379
  this.core = await Core.open(this.storage, {
374
380
  compat: opts.compat,
375
381
  force: opts.force,
382
+ sessions: this.sessions,
376
383
  createIfMissing: opts.createIfMissing,
377
384
  readonly: unlocked,
378
385
  overwrite: opts.overwrite,
@@ -456,6 +463,7 @@ module.exports = class Hypercore extends EventEmitter {
456
463
  if (i === -1) return
457
464
 
458
465
  this.sessions.splice(i, 1)
466
+ this.core.active--
459
467
  this.readable = false
460
468
  this.writable = false
461
469
  this.closed = true
@@ -470,14 +478,14 @@ module.exports = class Hypercore extends EventEmitter {
470
478
  if (this.replicator !== null) {
471
479
  this.replicator.findingPeers -= this._findingPeers
472
480
  this.replicator.clearRequests(this.activeRequests, err)
473
- this.replicator.updateActivity(this._active ? -1 : 0, this)
481
+ this.replicator.updateActivity(this._active ? -1 : 0)
474
482
  }
475
483
 
476
484
  this._findingPeers = 0
477
485
 
478
- if (this.sessions.length) {
486
+ if (this.sessions.length || this.core.active > 0) {
479
487
  // if this is the last session and we are auto closing, trigger that first to enforce error handling
480
- if (this.sessions.length === 1 && this.autoClose) await this.sessions[0].close(err)
488
+ if (this.sessions.length === 1 && this.core.active === 1 && this.autoClose) await this.sessions[0].close(err)
481
489
  // emit "fake" close as this is a session
482
490
  this.emit('close', false)
483
491
  return
@@ -527,8 +535,7 @@ module.exports = class Hypercore extends EventEmitter {
527
535
  _attachToMuxerOpened (mux, useSession) {
528
536
  // If the user wants to, we can make this replication run in a session
529
537
  // that way the core wont close "under them" during replication
530
- const session = useSession ? this.session({ active: false }) : null
531
- this.replicator.attachTo(mux, session)
538
+ this.replicator.attachTo(mux, useSession)
532
539
  }
533
540
 
534
541
  get discoveryKey () {
package/lib/core.js CHANGED
@@ -15,7 +15,7 @@ const audit = require('./audit')
15
15
  const { createTracer } = require('hypertrace')
16
16
 
17
17
  module.exports = class Core {
18
- constructor (header, compat, crypto, oplog, bigHeader, tree, blocks, bitfield, verifier, legacy, onupdate, onconflict) {
18
+ constructor (header, compat, crypto, oplog, bigHeader, tree, blocks, bitfield, verifier, sessions, legacy, onupdate, onconflict) {
19
19
  this.tracer = createTracer(this)
20
20
  this.onupdate = onupdate
21
21
  this.onconflict = onconflict
@@ -33,6 +33,8 @@ module.exports = class Core {
33
33
  this.updating = false
34
34
  this.closed = false
35
35
  this.skipBitfield = null
36
+ this.active = sessions.length
37
+ this.sessions = sessions
36
38
 
37
39
  this._manifestFlushed = !!header.manifest
38
40
  this._maxOplogSize = 65536
@@ -192,7 +194,7 @@ module.exports = class Core {
192
194
  }
193
195
  }
194
196
 
195
- return new this(header, compat, crypto, oplog, bigHeader, tree, blocks, bitfield, verifier, legacy, opts.onupdate || noop, opts.onconflict || noop)
197
+ return new this(header, compat, crypto, oplog, bigHeader, tree, blocks, bitfield, verifier, opts.sessions || [], legacy, opts.onupdate || noop, opts.onconflict || noop)
196
198
  }
197
199
 
198
200
  async audit () {
@@ -43,6 +43,13 @@ module.exports = class ReceiverQueue {
43
43
  return null
44
44
  }
45
45
 
46
+ clear () {
47
+ this.queue.clear()
48
+ this.priority = []
49
+ this.length = 0
50
+ this.requests.clear()
51
+ }
52
+
46
53
  delete (id) {
47
54
  const req = this.requests.get(id)
48
55
  if (!req) return
@@ -146,6 +146,8 @@ class RemoteBitfieldSegment {
146
146
  }
147
147
 
148
148
  module.exports = class RemoteBitfield {
149
+ static BITS_PER_PAGE = BITS_PER_PAGE
150
+
149
151
  constructor () {
150
152
  this._pages = new BigSparseArray()
151
153
  this._segments = new BigSparseArray()
package/lib/replicator.js CHANGED
@@ -232,7 +232,6 @@ class InflightTracker {
232
232
 
233
233
  add (req) {
234
234
  const id = this._free.length ? this._free.pop() : this._requests.push(null)
235
-
236
235
  req.id = id
237
236
  this._requests[id - 1] = req
238
237
  return req
@@ -242,14 +241,14 @@ class InflightTracker {
242
241
  return id <= this._requests.length ? this._requests[id - 1] : null
243
242
  }
244
243
 
245
- remove (id) {
246
- if (id <= this._requests.length) {
247
- const req = this._requests[id - 1]
248
- clearTimeout(req.timeout)
249
- req.timeout = null
250
- this._requests[id - 1] = null
251
- this._free.push(id)
252
- }
244
+ remove (id, roundtrip) {
245
+ if (id > this._requests.length) return
246
+ this._requests[id - 1] = null
247
+ if (roundtrip === true) this._free.push(id)
248
+ }
249
+
250
+ reusable (id) {
251
+ this._free.push(id)
253
252
  }
254
253
  }
255
254
 
@@ -291,8 +290,38 @@ class BlockTracker {
291
290
  }
292
291
  }
293
292
 
293
+ class RoundtripQueue {
294
+ constructor () {
295
+ this.queue = []
296
+ this.tick = 0
297
+ }
298
+
299
+ clear () {
300
+ const q = this.queue
301
+ this.queue = []
302
+ return q
303
+ }
304
+
305
+ add (id) {
306
+ this.queue.push([++this.tick, id])
307
+ }
308
+
309
+ flush (tick) {
310
+ let flushed = null
311
+
312
+ for (let i = 0; i < this.queue.length; i++) {
313
+ if (this.queue[i][0] > tick) break
314
+ if (flushed === null) flushed = []
315
+ flushed.push(this.queue[i][1])
316
+ }
317
+
318
+ if (flushed !== null) this.queue.splice(0, flushed.length)
319
+ return flushed
320
+ }
321
+ }
322
+
294
323
  class Peer {
295
- constructor (replicator, protomux, channel, session, inflightRange) {
324
+ constructor (replicator, protomux, channel, useSession, inflightRange) {
296
325
  this.tracer = createTracer(this, { parent: replicator.core.tracer })
297
326
  this.core = replicator.core
298
327
  this.replicator = replicator
@@ -303,8 +332,9 @@ class Peer {
303
332
  this.inflightRange = inflightRange
304
333
 
305
334
  this.paused = false
335
+ this.removed = false
306
336
 
307
- this.session = session
337
+ this.useSession = useSession
308
338
 
309
339
  this.channel = channel
310
340
  this.channel.userData = this
@@ -323,6 +353,9 @@ class Peer {
323
353
  this.receiverQueue = new ReceiverQueue()
324
354
  this.receiverBusy = false
325
355
 
356
+ // most often not used, so made on demand
357
+ this.roundtripQueue = null
358
+
326
359
  this.inflight = 0
327
360
  this.dataProcessing = 0
328
361
 
@@ -348,6 +381,7 @@ class Peer {
348
381
  this.remoteDownloading = true
349
382
  this.remoteSynced = false
350
383
  this.remoteHasManifest = false
384
+ this.remoteRequests = new Map()
351
385
 
352
386
  this.segmentsWanted = new Set()
353
387
  this.broadcastedNonSparse = false
@@ -386,6 +420,19 @@ class Peer {
386
420
  if (drop) this._unclearLocalRange(start, length)
387
421
  else this._clearLocalRange(start, length)
388
422
 
423
+ // TODO: consider also adding early-returns on the drop===true case
424
+ if (!drop) {
425
+ // No need to broadcast if the remote already has this range
426
+
427
+ if (this._remoteContiguousLength >= start + length) return
428
+
429
+ if (length === 1) {
430
+ if (this.remoteBitfield.get(start)) return
431
+ } else {
432
+ if (this.remoteBitfield.firstUnset(start) >= start + length) return
433
+ }
434
+ }
435
+
389
436
  this.wireRange.send({
390
437
  drop,
391
438
  start,
@@ -467,7 +514,7 @@ class Peer {
467
514
  const reopen = isRemote === true && this.remoteOpened === true && this.remoteDownloading === false &&
468
515
  this.remoteUploading === true && this.replicator.downloading === true
469
516
 
470
- if (this.session && !reopen) this.replicator._closeSession(this.session)
517
+ if (this.useSession && !reopen) this.replicator._closeSession()
471
518
 
472
519
  if (this.remoteOpened === false) {
473
520
  this.replicator._ifAvailable--
@@ -476,10 +523,18 @@ class Peer {
476
523
  }
477
524
 
478
525
  this.remoteOpened = false
526
+ this.removed = true
527
+ this.remoteRequests.clear() // cancel all
528
+ this.receiverQueue.clear()
529
+
530
+ if (this.roundtripQueue !== null) {
531
+ for (const id of this.roundtripQueue.clear()) this.replicator._inflight.reusable(id)
532
+ }
533
+
479
534
  this.replicator._removePeer(this)
480
535
 
481
536
  if (reopen) {
482
- this.replicator._makePeer(this.protomux, this.session)
537
+ this.replicator._makePeer(this.protomux, this.useSession)
483
538
  }
484
539
  }
485
540
 
@@ -598,6 +653,15 @@ class Peer {
598
653
  async onrequest (msg) {
599
654
  this.tracer.trace('onrequest', msg)
600
655
 
656
+ const size = this.remoteRequests.size
657
+ this.remoteRequests.set(msg.id, msg)
658
+
659
+ // if size didnt change -> id overwrite -> old one is deleted, cancel current and re-add
660
+ if (size === this.remoteRequests.size) {
661
+ this._cancel(msg.id)
662
+ this.remoteRequests.set(msg.id, msg)
663
+ }
664
+
601
665
  if (!this.protomux.drained || this.receiverQueue.length) {
602
666
  this.receiverQueue.push(msg)
603
667
  return
@@ -607,7 +671,12 @@ class Peer {
607
671
  }
608
672
 
609
673
  oncancel (msg) {
610
- this.receiverQueue.delete(msg.request)
674
+ this._cancel(msg.request)
675
+ }
676
+
677
+ _cancel (id) {
678
+ this.remoteRequests.delete(id)
679
+ this.receiverQueue.delete(id)
611
680
  }
612
681
 
613
682
  ondrain () {
@@ -617,12 +686,14 @@ class Peer {
617
686
  async _handleRequests () {
618
687
  if (this.receiverBusy) return
619
688
  this.receiverBusy = true
689
+ this.protomux.cork()
620
690
 
621
- while (this.remoteOpened && this.protomux.drained && this.receiverQueue.length > 0) {
691
+ while (this.remoteOpened && this.protomux.drained && this.receiverQueue.length > 0 && !this.removed) {
622
692
  const msg = this.receiverQueue.shift()
623
693
  await this._handleRequest(msg)
624
694
  }
625
695
 
696
+ this.protomux.uncork()
626
697
  this.receiverBusy = false
627
698
  }
628
699
 
@@ -639,6 +710,14 @@ class Peer {
639
710
  }
640
711
  }
641
712
 
713
+ // if cancelled do not reply
714
+ if (this.remoteRequests.get(msg.id) !== msg) {
715
+ return
716
+ }
717
+
718
+ // sync from now on, so safe to delete from the map
719
+ this.remoteRequests.delete(msg.id)
720
+
642
721
  if (proof === null) {
643
722
  if (msg.manifest && this.core.header.manifest) {
644
723
  const manifest = this.core.header.manifest
@@ -670,9 +749,11 @@ class Peer {
670
749
  if (!req) return
671
750
 
672
751
  this.inflight--
673
- this.replicator._removeInflight(id)
752
+ this.replicator._removeInflight(id, false)
674
753
  if (isBlockRequest(req)) this.replicator._unmarkInflight(req.block.index)
675
754
 
755
+ if (this.roundtripQueue === null) this.roundtripQueue = new RoundtripQueue()
756
+ this.roundtripQueue.add(id)
676
757
  this.wireCancel.send({ request: id })
677
758
  }
678
759
 
@@ -713,8 +794,7 @@ class Peer {
713
794
 
714
795
  if (req !== null) {
715
796
  if (req.peer !== this) return
716
- this.inflight--
717
- this.replicator._removeInflight(req.id)
797
+ this._onrequestroundtrip(req)
718
798
  }
719
799
 
720
800
  try {
@@ -774,11 +854,19 @@ class Peer {
774
854
 
775
855
  if (req === null || req.peer !== this) return
776
856
 
777
- this.inflight--
778
- this.replicator._removeInflight(req.id)
857
+ this._onrequestroundtrip(req)
779
858
  this.replicator._onnodata(this, req)
780
859
  }
781
860
 
861
+ _onrequestroundtrip (req) {
862
+ this.inflight--
863
+ this.replicator._removeInflight(req.id, true)
864
+ if (this.roundtripQueue === null) return
865
+ const flushed = this.roundtripQueue.flush(req.rt)
866
+ if (flushed === null) return
867
+ for (const id of flushed) this.replicator._inflight.reusable(id)
868
+ }
869
+
782
870
  onwant ({ start, length }) {
783
871
  this.replicator._onwant(this, start, length)
784
872
  }
@@ -928,6 +1016,7 @@ class Peer {
928
1016
 
929
1017
  return {
930
1018
  peer: this,
1019
+ rt: this.roundtripQueue === null ? 0 : this.roundtripQueue.tick,
931
1020
  id: 0,
932
1021
  fork: this.remoteFork,
933
1022
  block: null,
@@ -1327,19 +1416,19 @@ module.exports = class Replicator {
1327
1416
  return this.downloading || !this._inflight.idle
1328
1417
  }
1329
1418
 
1330
- setDownloading (downloading, session) {
1419
+ setDownloading (downloading) {
1331
1420
  clearTimeout(this._downloadingTimer)
1332
1421
 
1333
1422
  if (this.destroyed) return
1334
1423
  if (downloading || this._notDownloadingLinger === 0) {
1335
- this.setDownloadingNow(downloading, session)
1424
+ this.setDownloadingNow(downloading)
1336
1425
  return
1337
1426
  }
1338
1427
 
1339
- this._downloadingTimer = setTimeout(setDownloadingLater, this._notDownloadingLinger, this, downloading, session)
1428
+ this._downloadingTimer = setTimeout(setDownloadingLater, this._notDownloadingLinger, this, downloading)
1340
1429
  }
1341
1430
 
1342
- setDownloadingNow (downloading, session) {
1431
+ setDownloadingNow (downloading) {
1343
1432
  this._downloadingTimer = null
1344
1433
  if (this.downloading === downloading) return
1345
1434
  this.downloading = downloading
@@ -1351,7 +1440,7 @@ module.exports = class Replicator {
1351
1440
  for (const protomux of this._attached) {
1352
1441
  if (!protomux.stream.handshakeHash) continue
1353
1442
  if (protomux.opened({ protocol: 'hypercore/alpha', id: this.discoveryKey })) continue
1354
- this._makePeer(protomux, session && session.session({ active: false }))
1443
+ this._makePeer(protomux, true)
1355
1444
  }
1356
1445
  } else {
1357
1446
  for (const peer of this.peers) peer.closeIfIdle()
@@ -1605,21 +1694,20 @@ module.exports = class Replicator {
1605
1694
  this.onpeerupdate(true, peer)
1606
1695
  }
1607
1696
 
1608
- _removeInflight (id) {
1609
- this._inflight.remove(id)
1697
+ _removeInflight (id, roundtrip) {
1698
+ this._inflight.remove(id, roundtrip)
1610
1699
  if (this.isDownloading() === true) return
1611
1700
  for (const peer of this.peers) peer.signalUpgrade()
1612
1701
  }
1613
1702
 
1614
1703
  _removePeer (peer) {
1615
1704
  this.peers.splice(this.peers.indexOf(peer), 1)
1616
- peer.removed = true
1617
1705
 
1618
1706
  if (this._manifestPeer === peer) this._manifestPeer = null
1619
1707
 
1620
1708
  for (const req of this._inflight) {
1621
1709
  if (req.peer !== peer) continue
1622
- this._inflight.remove(req.id)
1710
+ this._inflight.remove(req.id, true)
1623
1711
  this._clearRequest(peer, req)
1624
1712
  }
1625
1713
 
@@ -1634,7 +1722,7 @@ module.exports = class Replicator {
1634
1722
  }
1635
1723
 
1636
1724
  _resolveHashLocally (peer, req) {
1637
- this._removeInflight(req.id)
1725
+ this._removeInflight(req.id, false)
1638
1726
  this._resolveBlockRequest(this._hashes, req.hash.index / 2, null, req)
1639
1727
  this.updatePeer(peer)
1640
1728
  }
@@ -2086,16 +2174,32 @@ module.exports = class Replicator {
2086
2174
  this._maybeResolveIfAvailableRanges()
2087
2175
  }
2088
2176
 
2089
- _closeSession (session) {
2090
- session.close().catch(safetyCatch)
2177
+ _closeSession () {
2178
+ this.core.active--
2179
+
2180
+ // we were the last active ref, so lets shut things down
2181
+ if (this.core.active === 0 && this.core.sessions.length === 0) {
2182
+ this.destroy()
2183
+ this.core.close().catch(safetyCatch)
2184
+ return
2185
+ }
2186
+
2187
+ // in case one session is still alive but its been marked for auto close also kill it
2188
+ if (this.core.sessions.length === 1 && this.core.active === 1 && this.core.sessions[0].autoClose) {
2189
+ this.core.sessions[0].close().catch(safetyCatch)
2190
+ }
2091
2191
  }
2092
2192
 
2093
2193
  attached (protomux) {
2094
2194
  return this._attached.has(protomux)
2095
2195
  }
2096
2196
 
2097
- attachTo (protomux, session) {
2098
- const makePeer = this._makePeer.bind(this, protomux, session)
2197
+ attachTo (protomux, useSession) {
2198
+ if (useSession) {
2199
+ this.core.active++
2200
+ }
2201
+
2202
+ const makePeer = this._makePeer.bind(this, protomux, useSession)
2099
2203
 
2100
2204
  this._attached.add(protomux)
2101
2205
  protomux.pair({ protocol: 'hypercore/alpha', id: this.discoveryKey }, makePeer)
@@ -2107,7 +2211,7 @@ module.exports = class Replicator {
2107
2211
  this._ifAvailable--
2108
2212
 
2109
2213
  if (opened && !this.destroyed) makePeer()
2110
- else if (session) this._closeSession(session)
2214
+ else if (useSession) this._closeSession()
2111
2215
  this._checkUpgradeIfAvailable()
2112
2216
  })
2113
2217
  }
@@ -2134,7 +2238,7 @@ module.exports = class Replicator {
2134
2238
  }
2135
2239
  }
2136
2240
 
2137
- _makePeer (protomux, session) {
2241
+ _makePeer (protomux, useSession) {
2138
2242
  const replicator = this
2139
2243
  if (protomux.opened({ protocol: 'hypercore/alpha', id: this.discoveryKey })) return onnochannel()
2140
2244
 
@@ -2163,7 +2267,7 @@ module.exports = class Replicator {
2163
2267
 
2164
2268
  if (channel === null) return onnochannel()
2165
2269
 
2166
- const peer = new Peer(replicator, protomux, channel, session, this.inflightRange)
2270
+ const peer = new Peer(replicator, protomux, channel, useSession, this.inflightRange)
2167
2271
  const stream = protomux.stream
2168
2272
 
2169
2273
  peer.channel.open({
@@ -2174,7 +2278,7 @@ module.exports = class Replicator {
2174
2278
  return true
2175
2279
 
2176
2280
  function onnochannel () {
2177
- if (session) replicator._closeSession(session)
2281
+ if (useSession) replicator._closeSession()
2178
2282
  return false
2179
2283
  }
2180
2284
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "10.37.1",
3
+ "version": "10.37.3",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {