hypercore 11.35.0 → 11.35.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 (2) hide show
  1. package/lib/replicator.js +115 -38
  2. package/package.json +1 -1
package/lib/replicator.js CHANGED
@@ -285,6 +285,10 @@ class RangeRequest extends Attachable {
285
285
  this.ranges[rangeIndex] = h
286
286
  }
287
287
 
288
+ if (!this.resolved && this.replicator._updatesRunning) {
289
+ this.replicator._updatesQueued = true
290
+ }
291
+
288
292
  if (this.end === -1) {
289
293
  this.replicator._alwaysLatestBlock--
290
294
  }
@@ -974,7 +978,8 @@ class Peer {
974
978
  this.remoteOpened &&
975
979
  this.protomux.drained &&
976
980
  this.receiverQueue.length > 0 &&
977
- !this.removed
981
+ !this.removed &&
982
+ this.core.closing === null
978
983
  ) {
979
984
  const msg = this.receiverQueue.shift()
980
985
  await this._handleRequest(msg)
@@ -985,6 +990,7 @@ class Peer {
985
990
  }
986
991
 
987
992
  async push(index) {
993
+ if (this.core.closing !== null) return
988
994
  if (!this.remoteAllowPush) return
989
995
  if (!this.remoteDownloading) return
990
996
  if (this.core.state.length <= index) return
@@ -1265,7 +1271,7 @@ class Peer {
1265
1271
  return
1266
1272
  }
1267
1273
 
1268
- if (this.core.closed && !isCriticalError(err)) return
1274
+ if ((this.core.closing !== null || this.core.closed) && !isCriticalError(err)) return
1269
1275
 
1270
1276
  if (err.code !== 'INVALID_OPERATION') {
1271
1277
  // might be a fork, verify
@@ -1401,6 +1407,8 @@ class Peer {
1401
1407
  }
1402
1408
 
1403
1409
  async onrange({ drop, start, length }) {
1410
+ if (this.core.closing !== null) return
1411
+
1404
1412
  const has = drop === false
1405
1413
 
1406
1414
  if (drop === true && start < this._remoteContiguousLength) {
@@ -1541,8 +1549,8 @@ class Peer {
1541
1549
  }
1542
1550
 
1543
1551
  _requestSeek(s) {
1544
- // if replicator is updating the seeks etc, bail and wait for it to drain
1545
- if (this.replicator._updatesPending > 0) return false
1552
+ // if replicator is updating the seeks, bail and wait for it to drain
1553
+ if (this.replicator._updatingSeeks) return false
1546
1554
  if (this.replicator.pushOnly) return false
1547
1555
 
1548
1556
  const { length, fork } = this.core.state
@@ -1952,6 +1960,7 @@ class Peer {
1952
1960
  }
1953
1961
  }
1954
1962
  } catch (err) {
1963
+ if (this.core.closing !== null) return
1955
1964
  this.stream.destroy(err)
1956
1965
  return
1957
1966
  }
@@ -2028,7 +2037,9 @@ module.exports = class Replicator {
2028
2037
  this._hadPeers = false
2029
2038
  this._active = 0
2030
2039
  this._ifAvailable = 0
2031
- this._updatesPending = 0
2040
+ this._updatingSeeks = false
2041
+ this._updatesRunning = false
2042
+ this._updatesQueued = false
2032
2043
  this._applyingReorg = null
2033
2044
  this._manifestPeer = null
2034
2045
  this._notDownloadingLinger = notDownloadingLinger
@@ -2614,55 +2625,117 @@ module.exports = class Replicator {
2614
2625
  }
2615
2626
  }
2616
2627
 
2628
+ _updateRanges(index, limit) {
2629
+ index = Math.min(index, this._ranges.length - 1)
2630
+
2631
+ let checked = 0
2632
+ let resolved = 0
2633
+ let updateAll = false
2634
+ let remaining = Math.min(limit, this._ranges.length)
2635
+
2636
+ while (remaining-- > 0 && this._ranges.length > 0) {
2637
+ if (index >= this._ranges.length) index = 0
2638
+
2639
+ const r = this._ranges[index]
2640
+
2641
+ clampRange(this.core, r)
2642
+ checked++
2643
+
2644
+ if (r.end !== -1 && r.start >= r.end) {
2645
+ this._resolveRangeRequest(r)
2646
+ resolved++
2647
+ // Crossing into the capped window exposes another range to peer scheduling.
2648
+ if (this._ranges.length === MAX_RANGES) updateAll = true
2649
+ } else {
2650
+ index++
2651
+ }
2652
+ }
2653
+
2654
+ index = this._ranges.length === 0 ? 0 : index % this._ranges.length
2655
+
2656
+ return { checked, resolved, index, updateAll }
2657
+ }
2658
+
2617
2659
  // "slow" updates here - async but not allowed to ever throw
2618
2660
  async _updateNonPrimary(updateAll) {
2619
- // Check if running, if so skip it and the running one will issue another update for us (debounce)
2620
- while (++this._updatesPending === 1) {
2621
- let len = Math.min(MAX_RANGES, this._ranges.length)
2661
+ this._updatesQueued = true
2662
+ if (this._updatesRunning) {
2663
+ if (this._inflight.idle || updateAll) this.queueUpdateAll()
2664
+ return
2665
+ }
2622
2666
 
2623
- for (let i = 0; i < len; i++) {
2624
- const r = this._ranges[i]
2667
+ this._updatesRunning = true
2625
2668
 
2626
- clampRange(this.core, r)
2669
+ while (this._updatesQueued) {
2670
+ this._updatesQueued = false
2627
2671
 
2628
- if (r.end !== -1 && r.start >= r.end) {
2629
- this._resolveRangeRequest(r)
2630
- i--
2631
- if (len > this._ranges.length) len--
2632
- if (this._ranges.length === MAX_RANGES) updateAll = true
2633
- }
2634
- }
2672
+ let checkedSinceResolve = 0
2673
+ let rangeIndex = 0
2674
+ const drain = this._inflight.idle || updateAll
2675
+ const checkedSeeks = new Set()
2635
2676
 
2636
- for (let i = 0; i < this._seeks.length; i++) {
2637
- const s = this._seeks[i]
2677
+ while (true) {
2678
+ const limit = Math.min(MAX_RANGES, this._ranges.length - checkedSinceResolve)
2679
+ const {
2680
+ checked,
2681
+ resolved,
2682
+ index,
2683
+ updateAll: resultUpdateAll
2684
+ } = this._updateRanges(rangeIndex, limit)
2685
+ rangeIndex = index
2638
2686
 
2639
- let err = null
2640
- let res = null
2687
+ if (resultUpdateAll) updateAll = true
2641
2688
 
2642
- try {
2643
- res = await s.seeker.update()
2644
- } catch (error) {
2645
- err = error
2646
- }
2689
+ if (resolved > 0) checkedSinceResolve = 0
2690
+ else checkedSinceResolve += checked
2691
+
2692
+ const continueRanges = checked > 0 && checkedSinceResolve < this._ranges.length && drain
2693
+
2694
+ this._updatingSeeks = true
2695
+
2696
+ let checkedSeek = false
2697
+ for (const s of this._seeks.slice()) {
2698
+ if (checkedSeeks.has(s) || !this._seeks.includes(s)) continue
2699
+ checkedSeeks.add(s)
2700
+ checkedSeek = true
2701
+
2702
+ let err = null
2703
+ let res = null
2704
+
2705
+ try {
2706
+ res = await s.seeker.update()
2707
+ } catch (error) {
2708
+ err = error
2709
+ }
2647
2710
 
2648
- if (!res && !err) continue
2711
+ const seekIndex = this._seeks.indexOf(s)
2712
+ if (seekIndex === -1 || (!res && !err)) continue
2649
2713
 
2650
- if (i < this._seeks.length - 1) this._seeks[i] = this._seeks.pop()
2651
- else this._seeks.pop()
2714
+ const h = this._seeks.pop()
2715
+ if (h !== s) this._seeks[seekIndex] = h
2652
2716
 
2653
- i--
2717
+ if (err) s.reject(err)
2718
+ else s.resolve(res)
2719
+ }
2720
+
2721
+ this._updatingSeeks = false
2722
+
2723
+ if (
2724
+ (!continueRanges || (checkedSeek && this._seeks.length > 0)) &&
2725
+ (this._inflight.idle || updateAll)
2726
+ ) {
2727
+ this.queueUpdateAll()
2728
+ }
2729
+ if (!continueRanges) break
2654
2730
 
2655
- if (err) s.reject(err)
2656
- else s.resolve(res)
2731
+ await yieldToLoop()
2732
+ if (this.destroyed) break
2657
2733
  }
2658
2734
 
2659
- // No additional updates scheduled - break
2660
- if (--this._updatesPending === 0) break
2661
- // Debounce the additional updates - continue
2662
- this._updatesPending = 0
2735
+ if (this.destroyed) break
2663
2736
  }
2664
2737
 
2665
- if (this._inflight.idle || updateAll) this.queueUpdateAll()
2738
+ this._updatesRunning = false
2666
2739
  }
2667
2740
 
2668
2741
  _clearRequest(peer, req) {
@@ -3398,6 +3471,10 @@ function incrementRx(stats1, stats2) {
3398
3471
 
3399
3472
  function noop() {}
3400
3473
 
3474
+ function yieldToLoop() {
3475
+ return new Promise((resolve) => setImmediate(resolve))
3476
+ }
3477
+
3401
3478
  function backoff(times) {
3402
3479
  const sleep = times < 2 ? 200 : times < 5 ? 500 : times < 40 ? 1000 : 5000
3403
3480
  return new Promise((resolve) => setTimeout(resolve, sleep))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypercore",
3
- "version": "11.35.0",
3
+ "version": "11.35.2",
4
4
  "description": "Hypercore is a secure, distributed append-only log",
5
5
  "main": "index.js",
6
6
  "scripts": {