hypercore 11.22.2 → 11.23.0
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/lib/bitfield.js +26 -16
- package/lib/messages.js +28 -4
- package/lib/remote-bitfield.js +1 -0
- package/lib/replicator.js +256 -90
- package/lib/wants.js +307 -0
- package/package.json +1 -1
package/lib/bitfield.js
CHANGED
|
@@ -420,6 +420,26 @@ module.exports = class Bitfield {
|
|
|
420
420
|
return this.findLast(false, position)
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
hasUnset(start, length) {
|
|
424
|
+
const end = start + length
|
|
425
|
+
|
|
426
|
+
let j = start & (BITS_PER_SEGMENT - 1)
|
|
427
|
+
let i = (start - j) / BITS_PER_SEGMENT
|
|
428
|
+
|
|
429
|
+
while (i < this._segments.maxLength) {
|
|
430
|
+
const s = this._segments.get(i)
|
|
431
|
+
const index = s ? s.findFirst(false, j) : j
|
|
432
|
+
if (index !== -1) return i * BITS_PER_SEGMENT + index < end
|
|
433
|
+
|
|
434
|
+
j = 0
|
|
435
|
+
i++
|
|
436
|
+
|
|
437
|
+
if (i * BITS_PER_SEGMENT >= end) return false
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return true
|
|
441
|
+
}
|
|
442
|
+
|
|
423
443
|
hasSet(start, length) {
|
|
424
444
|
const end = start + length
|
|
425
445
|
|
|
@@ -475,23 +495,23 @@ module.exports = class Bitfield {
|
|
|
475
495
|
}
|
|
476
496
|
|
|
477
497
|
*want(start, length) {
|
|
478
|
-
|
|
498
|
+
let j = start & (BITS_PER_SEGMENT - 1)
|
|
479
499
|
let i = (start - j) / BITS_PER_SEGMENT
|
|
480
500
|
|
|
481
501
|
while (length > 0) {
|
|
482
502
|
const s = this._segments.get(i)
|
|
483
503
|
|
|
484
504
|
if (s) {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const end = ceilTo(clamp(length / 8, 4096, BYTES_PER_SEGMENT), 4096)
|
|
505
|
+
let end = Math.min(j + length, BITS_PER_SEGMENT)
|
|
506
|
+
if (end & 31) end += 32 - (end & 31)
|
|
488
507
|
|
|
489
508
|
yield {
|
|
490
|
-
start: i * BITS_PER_SEGMENT,
|
|
491
|
-
bitfield: s.bitfield.subarray(
|
|
509
|
+
start: i * BITS_PER_SEGMENT + j,
|
|
510
|
+
bitfield: s.bitfield.subarray(j / 32, end / 32)
|
|
492
511
|
}
|
|
493
512
|
}
|
|
494
513
|
|
|
514
|
+
j = 0
|
|
495
515
|
i++
|
|
496
516
|
length -= BITS_PER_SEGMENT
|
|
497
517
|
}
|
|
@@ -521,13 +541,3 @@ module.exports = class Bitfield {
|
|
|
521
541
|
return new Bitfield(buffer)
|
|
522
542
|
}
|
|
523
543
|
}
|
|
524
|
-
|
|
525
|
-
function clamp(n, min, max) {
|
|
526
|
-
return Math.min(Math.max(n, min), max)
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
function ceilTo(n, multiple = 1) {
|
|
530
|
-
const remainder = n % multiple
|
|
531
|
-
if (remainder === 0) return n
|
|
532
|
-
return n + multiple - remainder
|
|
533
|
-
}
|
package/lib/messages.js
CHANGED
|
@@ -543,19 +543,32 @@ wire.noData = {
|
|
|
543
543
|
}
|
|
544
544
|
}
|
|
545
545
|
|
|
546
|
+
// TODO: we should move to having these have a handle like in data, easier since they
|
|
547
|
+
// allocate state in practice
|
|
546
548
|
wire.want = {
|
|
547
549
|
preencode(state, m) {
|
|
548
550
|
c.uint.preencode(state, m.start)
|
|
549
551
|
c.uint.preencode(state, m.length)
|
|
552
|
+
|
|
553
|
+
const flags = m.any ? 1 : 0
|
|
554
|
+
if (flags !== 0) c.uint.preencode(state, flags)
|
|
550
555
|
},
|
|
551
556
|
encode(state, m) {
|
|
552
557
|
c.uint.encode(state, m.start)
|
|
553
558
|
c.uint.encode(state, m.length)
|
|
559
|
+
|
|
560
|
+
const flags = m.any ? 1 : 0
|
|
561
|
+
if (flags !== 0) c.uint.encode(state, flags)
|
|
554
562
|
},
|
|
555
563
|
decode(state) {
|
|
564
|
+
const start = c.uint.decode(state)
|
|
565
|
+
const length = c.uint.decode(state)
|
|
566
|
+
const flags = state.start < state.end ? c.uint.decode(state) : 0
|
|
567
|
+
|
|
556
568
|
return {
|
|
557
|
-
start
|
|
558
|
-
length
|
|
569
|
+
start,
|
|
570
|
+
length,
|
|
571
|
+
any: (flags & 1) !== 0
|
|
559
572
|
}
|
|
560
573
|
}
|
|
561
574
|
}
|
|
@@ -564,15 +577,26 @@ wire.unwant = {
|
|
|
564
577
|
preencode(state, m) {
|
|
565
578
|
c.uint.preencode(state, m.start)
|
|
566
579
|
c.uint.preencode(state, m.length)
|
|
580
|
+
|
|
581
|
+
const flags = m.any ? 1 : 0
|
|
582
|
+
if (flags !== 0) c.uint.preencode(state, flags)
|
|
567
583
|
},
|
|
568
584
|
encode(state, m) {
|
|
569
585
|
c.uint.encode(state, m.start)
|
|
570
586
|
c.uint.encode(state, m.length)
|
|
587
|
+
|
|
588
|
+
const flags = m.any ? 1 : 0
|
|
589
|
+
if (flags !== 0) c.uint.encode(state, flags)
|
|
571
590
|
},
|
|
572
591
|
decode(state, m) {
|
|
592
|
+
const start = c.uint.decode(state)
|
|
593
|
+
const length = c.uint.decode(state)
|
|
594
|
+
const flags = state.start < state.end ? c.uint.decode(state) : 0
|
|
595
|
+
|
|
573
596
|
return {
|
|
574
|
-
start
|
|
575
|
-
length
|
|
597
|
+
start,
|
|
598
|
+
length,
|
|
599
|
+
any: (flags & 1) !== 0
|
|
576
600
|
}
|
|
577
601
|
}
|
|
578
602
|
}
|
package/lib/remote-bitfield.js
CHANGED
package/lib/replicator.js
CHANGED
|
@@ -31,6 +31,7 @@ const ReceiverQueue = require('./receiver-queue')
|
|
|
31
31
|
const HotswapQueue = require('./hotswap-queue')
|
|
32
32
|
const RemoteBitfield = require('./remote-bitfield')
|
|
33
33
|
const { MerkleTree } = require('./merkle-tree')
|
|
34
|
+
const { LocalWants, RemoteWants, WANT_BATCH } = require('./wants')
|
|
34
35
|
const {
|
|
35
36
|
REQUEST_CANCELLED,
|
|
36
37
|
REQUEST_TIMEOUT,
|
|
@@ -43,11 +44,9 @@ const caps = require('./caps')
|
|
|
43
44
|
|
|
44
45
|
const DEFAULT_MAX_INFLIGHT = [16, 512]
|
|
45
46
|
const SCALE_LATENCY = 50
|
|
46
|
-
const DEFAULT_SEGMENT_SIZE = 256 * 1024 * 8 // 256 KiB in bits
|
|
47
47
|
const NOT_DOWNLOADING_SLACK = (20000 + Math.random() * 20000) | 0
|
|
48
48
|
const MAX_PEERS_UPGRADE = 3
|
|
49
49
|
const LAST_BLOCKS = 256
|
|
50
|
-
const MAX_REMOTE_SEGMENTS = 2048
|
|
51
50
|
|
|
52
51
|
const MAX_RANGES = 64
|
|
53
52
|
|
|
@@ -69,6 +68,17 @@ class Attachable {
|
|
|
69
68
|
this.resolved = false
|
|
70
69
|
this.processing = false
|
|
71
70
|
this.refs = []
|
|
71
|
+
this.wants = null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
addWant(w) {
|
|
75
|
+
if (this.wants === null) this.wants = new Set()
|
|
76
|
+
this.wants.add(w)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
removeWant(w) {
|
|
80
|
+
if (this.wants === null) return
|
|
81
|
+
this.wants.delete(w)
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
attach(session) {
|
|
@@ -118,7 +128,37 @@ class Attachable {
|
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
gc() {
|
|
121
|
-
if (this.refs.length
|
|
131
|
+
if (this.refs.length > 0 || this.processing) return
|
|
132
|
+
this._unref()
|
|
133
|
+
|
|
134
|
+
if (this.wants === null) return
|
|
135
|
+
|
|
136
|
+
const wants = this.wants
|
|
137
|
+
this.wants = null
|
|
138
|
+
|
|
139
|
+
let update = null
|
|
140
|
+
|
|
141
|
+
for (const w of wants) {
|
|
142
|
+
if (w.any) {
|
|
143
|
+
const removed = w.wants.removeAnyRange(w.start, w.length, this)
|
|
144
|
+
if (!removed) continue
|
|
145
|
+
if (update === null) update = new Set()
|
|
146
|
+
update.add(w.wants.peer)
|
|
147
|
+
// TODO: should also use a free list for simplicity
|
|
148
|
+
w.wants.peer.wireUnwant.send(removed)
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (w.wants.removeBatch(w.start, this)) {
|
|
153
|
+
if (update === null) update = new Set()
|
|
154
|
+
update.add(w.wants.peer)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (update === null) return
|
|
159
|
+
for (const peer of update) {
|
|
160
|
+
this.replicator.updatePeer(peer)
|
|
161
|
+
}
|
|
122
162
|
}
|
|
123
163
|
|
|
124
164
|
processed() {
|
|
@@ -139,6 +179,7 @@ class Attachable {
|
|
|
139
179
|
while (this.refs.length > 0) {
|
|
140
180
|
this._detach(this.refs[this.refs.length - 1]).resolve(val)
|
|
141
181
|
}
|
|
182
|
+
this.gc()
|
|
142
183
|
}
|
|
143
184
|
|
|
144
185
|
reject(err) {
|
|
@@ -146,6 +187,7 @@ class Attachable {
|
|
|
146
187
|
while (this.refs.length > 0) {
|
|
147
188
|
this._detach(this.refs[this.refs.length - 1]).reject(err)
|
|
148
189
|
}
|
|
190
|
+
this.gc()
|
|
149
191
|
}
|
|
150
192
|
|
|
151
193
|
setTimeout(r, ms) {
|
|
@@ -167,6 +209,7 @@ class BlockRequest extends Attachable {
|
|
|
167
209
|
}
|
|
168
210
|
|
|
169
211
|
_unref() {
|
|
212
|
+
if (this.resolved) return
|
|
170
213
|
this.queued = false
|
|
171
214
|
|
|
172
215
|
for (const req of this.inflight) {
|
|
@@ -178,6 +221,15 @@ class BlockRequest extends Attachable {
|
|
|
178
221
|
}
|
|
179
222
|
}
|
|
180
223
|
|
|
224
|
+
class RangeBatchRequest extends Attachable {
|
|
225
|
+
constructor(replicator, batch) {
|
|
226
|
+
super(replicator)
|
|
227
|
+
|
|
228
|
+
this.batch = batch
|
|
229
|
+
this.index = 0
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
181
233
|
class RangeRequest extends Attachable {
|
|
182
234
|
constructor(replicator, ranges, start, end, linear, ifAvailable, blocks) {
|
|
183
235
|
super(replicator)
|
|
@@ -188,6 +240,8 @@ class RangeRequest extends Attachable {
|
|
|
188
240
|
this.ifAvailable = ifAvailable
|
|
189
241
|
this.blocks = blocks
|
|
190
242
|
this.ranges = ranges
|
|
243
|
+
this.batches = []
|
|
244
|
+
|
|
191
245
|
ranges.push(this)
|
|
192
246
|
|
|
193
247
|
if (this.end === -1) {
|
|
@@ -199,6 +253,28 @@ class RangeRequest extends Attachable {
|
|
|
199
253
|
this.userEnd = end
|
|
200
254
|
}
|
|
201
255
|
|
|
256
|
+
addBatch(batch) {
|
|
257
|
+
for (let i = 0; i < this.batches.length; i++) {
|
|
258
|
+
const b = this.batches[i]
|
|
259
|
+
if (b.batch === batch) return false
|
|
260
|
+
}
|
|
261
|
+
const b = new RangeBatchRequest(this.replicator, batch)
|
|
262
|
+
b.index = this.batches.length
|
|
263
|
+
this.batches.push(b)
|
|
264
|
+
return true
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
removeBatch(batch) {
|
|
268
|
+
const head = this.batches.pop()
|
|
269
|
+
|
|
270
|
+
if (head !== batch) {
|
|
271
|
+
head.index = batch.index
|
|
272
|
+
this.batches[head.index] = head
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
batch.gc()
|
|
276
|
+
}
|
|
277
|
+
|
|
202
278
|
_unref() {
|
|
203
279
|
const rangeIndex = this.ranges.indexOf(this)
|
|
204
280
|
if (rangeIndex === -1) return
|
|
@@ -211,6 +287,10 @@ class RangeRequest extends Attachable {
|
|
|
211
287
|
if (this.end === -1) {
|
|
212
288
|
this.replicator._alwaysLatestBlock--
|
|
213
289
|
}
|
|
290
|
+
|
|
291
|
+
while (this.batches.length) {
|
|
292
|
+
this.batches.pop().gc()
|
|
293
|
+
}
|
|
214
294
|
}
|
|
215
295
|
|
|
216
296
|
_cancel(r) {
|
|
@@ -228,6 +308,7 @@ class UpgradeRequest extends Attachable {
|
|
|
228
308
|
}
|
|
229
309
|
|
|
230
310
|
_unref() {
|
|
311
|
+
if (this.resolved) return
|
|
231
312
|
if (this.replicator.eagerUpgrade === true || this.inflight.length > 0) return
|
|
232
313
|
this.replicator._upgrade = null
|
|
233
314
|
}
|
|
@@ -247,6 +328,7 @@ class SeekRequest extends Attachable {
|
|
|
247
328
|
}
|
|
248
329
|
|
|
249
330
|
_unref() {
|
|
331
|
+
if (this.resolved) return
|
|
250
332
|
if (this.inflight.length > 0) return
|
|
251
333
|
const i = this.seeks.indexOf(this)
|
|
252
334
|
if (i === -1) return
|
|
@@ -255,6 +337,15 @@ class SeekRequest extends Attachable {
|
|
|
255
337
|
}
|
|
256
338
|
}
|
|
257
339
|
|
|
340
|
+
class ForkRequest extends Attachable {
|
|
341
|
+
constructor(replicator, fork) {
|
|
342
|
+
super(replicator)
|
|
343
|
+
this.fork = fork
|
|
344
|
+
this.inflight = []
|
|
345
|
+
this.batch = null
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
258
349
|
class InflightTracker {
|
|
259
350
|
constructor() {
|
|
260
351
|
this._requests = []
|
|
@@ -400,7 +491,6 @@ class Peer {
|
|
|
400
491
|
this.remotePublicKey = this.stream.remotePublicKey
|
|
401
492
|
this.remoteSupportsSeeks = false
|
|
402
493
|
this.inflightRange = inflightRange
|
|
403
|
-
this.remoteSegmentsWanted = new Set()
|
|
404
494
|
this.fullyDownloadedSignaled = 0
|
|
405
495
|
|
|
406
496
|
this.paused = false
|
|
@@ -427,6 +517,7 @@ class Peer {
|
|
|
427
517
|
wireCancel: { tx: 0, rx: 0 },
|
|
428
518
|
wireData: { tx: 0, rx: 0 },
|
|
429
519
|
wireWant: { tx: 0, rx: 0 },
|
|
520
|
+
wireUnwant: { tx: 0, rx: 0 },
|
|
430
521
|
wireBitfield: { tx: 0, rx: 0 },
|
|
431
522
|
wireRange: { tx: 0, rx: 0 },
|
|
432
523
|
wireExtension: { tx: 0, rx: 0 },
|
|
@@ -477,6 +568,9 @@ class Peer {
|
|
|
477
568
|
this.segmentsWanted = new Set()
|
|
478
569
|
this.broadcastedNonSparse = false
|
|
479
570
|
|
|
571
|
+
this.wants = new LocalWants(this)
|
|
572
|
+
this.remoteWants = new RemoteWants(this)
|
|
573
|
+
|
|
480
574
|
this.lengthAcked = 0
|
|
481
575
|
|
|
482
576
|
this.extensions = new Map()
|
|
@@ -525,16 +619,10 @@ class Peer {
|
|
|
525
619
|
if (drop) this._unclearLocalRange(start, length)
|
|
526
620
|
else this._clearLocalRange(start, length)
|
|
527
621
|
|
|
528
|
-
const i = Math.floor(start / DEFAULT_SEGMENT_SIZE)
|
|
529
622
|
const fullyContig = this.core.header.hints.contiguousLength === this.core.state.length
|
|
530
623
|
|
|
531
|
-
if (
|
|
532
|
-
|
|
533
|
-
!this.remoteSegmentsWanted.has(i) &&
|
|
534
|
-
!drop &&
|
|
535
|
-
!fullyContig
|
|
536
|
-
) {
|
|
537
|
-
return
|
|
624
|
+
if (start + LAST_BLOCKS < this.core.state.length && !drop && !fullyContig) {
|
|
625
|
+
if (!this.remoteWants.hasRange(start, length)) return
|
|
538
626
|
}
|
|
539
627
|
|
|
540
628
|
let force = false
|
|
@@ -1181,15 +1269,13 @@ class Peer {
|
|
|
1181
1269
|
for (const id of flushed) this.replicator._inflight.reusable(id)
|
|
1182
1270
|
}
|
|
1183
1271
|
|
|
1184
|
-
onwant(
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
this.remoteSegmentsWanted.add(i)
|
|
1188
|
-
this.replicator._onwant(this, start, length)
|
|
1272
|
+
onwant(range) {
|
|
1273
|
+
if (!this.remoteWants.add(range)) return
|
|
1274
|
+
this.replicator._onwant(this, range.start, range.length, range.any)
|
|
1189
1275
|
}
|
|
1190
1276
|
|
|
1191
|
-
onunwant() {
|
|
1192
|
-
|
|
1277
|
+
onunwant(range) {
|
|
1278
|
+
this.remoteWants.remove(range)
|
|
1193
1279
|
}
|
|
1194
1280
|
|
|
1195
1281
|
onbitfield({ start, bitfield }) {
|
|
@@ -1244,7 +1330,7 @@ class Peer {
|
|
|
1244
1330
|
return
|
|
1245
1331
|
}
|
|
1246
1332
|
|
|
1247
|
-
const rem = start &
|
|
1333
|
+
const rem = start & (RemoteBitfield.BITS_PER_SEGMENT - 1)
|
|
1248
1334
|
if (rem > 0) {
|
|
1249
1335
|
start -= rem
|
|
1250
1336
|
length += rem
|
|
@@ -1259,7 +1345,7 @@ class Peer {
|
|
|
1259
1345
|
this.missingBlocks.insert(start, remote.bitfield)
|
|
1260
1346
|
}
|
|
1261
1347
|
|
|
1262
|
-
start +=
|
|
1348
|
+
start += RemoteBitfield.BITS_PER_SEGMENT
|
|
1263
1349
|
}
|
|
1264
1350
|
|
|
1265
1351
|
this._clearLocalRange(fixedStart, length)
|
|
@@ -1459,7 +1545,7 @@ class Peer {
|
|
|
1459
1545
|
return true
|
|
1460
1546
|
}
|
|
1461
1547
|
|
|
1462
|
-
this.
|
|
1548
|
+
this._maybeWantAnyRange(s.seeker.start, len, s)
|
|
1463
1549
|
return false
|
|
1464
1550
|
}
|
|
1465
1551
|
|
|
@@ -1498,7 +1584,7 @@ class Peer {
|
|
|
1498
1584
|
const { length, fork } = this.core.state
|
|
1499
1585
|
|
|
1500
1586
|
if (this._remoteHasBlock(b.index) === false || fork !== this.remoteFork) {
|
|
1501
|
-
this._maybeWant(b.index)
|
|
1587
|
+
if (!this.core.bitfield.get(b.index)) this._maybeWant(b.index, b)
|
|
1502
1588
|
return false
|
|
1503
1589
|
}
|
|
1504
1590
|
|
|
@@ -1512,6 +1598,43 @@ class Peer {
|
|
|
1512
1598
|
return true
|
|
1513
1599
|
}
|
|
1514
1600
|
|
|
1601
|
+
_maybeWant(index, handle) {
|
|
1602
|
+
if (index < this.remoteContiguousLength) return
|
|
1603
|
+
|
|
1604
|
+
const res = this.wants.add(index, handle)
|
|
1605
|
+
if (!res) return
|
|
1606
|
+
|
|
1607
|
+
const batch = res.want && res.unwant
|
|
1608
|
+
if (batch) this.protomux.cork()
|
|
1609
|
+
|
|
1610
|
+
if (res.unwant) {
|
|
1611
|
+
this.wireUnwant.send(res.unwant)
|
|
1612
|
+
incrementTx(this.stats.wireUnwant, this.replicator.stats.wireUnwant)
|
|
1613
|
+
}
|
|
1614
|
+
if (res.want) {
|
|
1615
|
+
this.wireWant.send(res.want)
|
|
1616
|
+
incrementTx(this.stats.wireWant, this.replicator.stats.wireWant)
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
if (batch) this.protomux.uncork()
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
_maybeWantAnyRange(start, length, handle) {
|
|
1623
|
+
if (handle.wants) {
|
|
1624
|
+
for (const w of handle.wants) {
|
|
1625
|
+
if ((w.start !== start || w.length !== length) && w.any) {
|
|
1626
|
+
const req = this.wants.removeAnyRange(w.start, w.length, handle)
|
|
1627
|
+
if (req) this.wireUnwant.send(req)
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
if (start + length <= this.remoteContiguousLength) return
|
|
1633
|
+
|
|
1634
|
+
const req = this.wants.addAnyRange(start, length, handle)
|
|
1635
|
+
if (req) this.wireWant.send(req)
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1515
1638
|
_requestRangeBlock(index, length) {
|
|
1516
1639
|
if (this.core.bitfield.get(index) === true || !this._canRequest(index)) return false
|
|
1517
1640
|
|
|
@@ -1548,68 +1671,119 @@ class Peer {
|
|
|
1548
1671
|
return this.missingBlocks.findFirst(true, i)
|
|
1549
1672
|
}
|
|
1550
1673
|
|
|
1551
|
-
|
|
1552
|
-
|
|
1674
|
+
_addRangeBatch(r, offset, end) {
|
|
1675
|
+
while (offset < end) {
|
|
1676
|
+
const next = this.core.bitfield.findFirst(false, offset)
|
|
1677
|
+
if (next === -1 || next >= end) return false
|
|
1553
1678
|
|
|
1554
|
-
|
|
1679
|
+
const b = (next - (next & (WANT_BATCH - 1))) / WANT_BATCH
|
|
1555
1680
|
|
|
1556
|
-
|
|
1557
|
-
let min = -1
|
|
1558
|
-
let max = -1
|
|
1681
|
+
if (r.addBatch(b)) return true
|
|
1559
1682
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
if (min === -1 || index < min) min = index
|
|
1563
|
-
if (max === -1 || index > max) max = index
|
|
1564
|
-
const has = index < this._remoteContiguousLength || this.missingBlocks.get(index) === true
|
|
1565
|
-
if (has === true && this._requestRangeBlock(index, length)) return true
|
|
1566
|
-
}
|
|
1683
|
+
offset = (b + 1) * WANT_BATCH
|
|
1684
|
+
}
|
|
1567
1685
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1686
|
+
return false
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
_populateRangeBatches(r) {
|
|
1690
|
+
for (let i = r.batches.length - 1; i >= 0; i--) {
|
|
1691
|
+
const b = r.batches[i]
|
|
1692
|
+
const minStart = b.batch * WANT_BATCH
|
|
1693
|
+
const maxEnd = minStart + WANT_BATCH
|
|
1694
|
+
|
|
1695
|
+
const start = Math.max(r.start, minStart)
|
|
1696
|
+
const end = Math.min(r.end === -1 ? maxEnd : r.end, maxEnd)
|
|
1697
|
+
|
|
1698
|
+
if (start < end && this.core.bitfield.hasUnset(start, end - start)) continue
|
|
1699
|
+
|
|
1700
|
+
r.removeBatch(b)
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
while (r.batches.length < 3) {
|
|
1704
|
+
const end = r.end === -1 ? this.core.state.length : r.end
|
|
1705
|
+
if (end <= r.start) return
|
|
1706
|
+
|
|
1707
|
+
const len = end - r.start
|
|
1708
|
+
const off = r.start + (r.linear ? 0 : Math.floor(Math.random() * len))
|
|
1709
|
+
|
|
1710
|
+
if (!this._addRangeBatch(r, off, end) && !this._addRangeBatch(r, r.start, end)) break
|
|
1570
1711
|
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
_requestRangeBatch(start, end, length) {
|
|
1715
|
+
let tries = this.inflight
|
|
1571
1716
|
|
|
1717
|
+
do {
|
|
1718
|
+
start = this._findNext(start)
|
|
1719
|
+
if (start === -1 || start >= end) break
|
|
1720
|
+
|
|
1721
|
+
if (this._requestRangeBlock(start, length)) return true
|
|
1722
|
+
start++
|
|
1723
|
+
} while (tries-- > 0)
|
|
1724
|
+
|
|
1725
|
+
return false
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
_getValidEnd(maxEnd) {
|
|
1572
1729
|
// if we can upgrade the remote, or the remote is ahead, then all the remotes blocks are valid
|
|
1573
1730
|
// otherwise truncate to the last length the remote has acked for us
|
|
1574
1731
|
const maxLocalLength =
|
|
1575
1732
|
this.canUpgrade || this.remoteLength >= this.core.state.length
|
|
1576
1733
|
? this.core.state.length
|
|
1577
|
-
: fork === this.lastUpgradableFork
|
|
1734
|
+
: this.core.state.fork === this.lastUpgradableFork
|
|
1578
1735
|
? Math.min(this.lastUpgradableLength, this.core.state.length)
|
|
1579
1736
|
: 0
|
|
1580
1737
|
|
|
1581
|
-
|
|
1738
|
+
return Math.min(
|
|
1582
1739
|
maxLocalLength,
|
|
1583
|
-
Math.min(
|
|
1740
|
+
Math.min(maxEnd === -1 ? this.remoteLength : maxEnd, this.remoteLength)
|
|
1584
1741
|
)
|
|
1585
|
-
|
|
1742
|
+
}
|
|
1586
1743
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1744
|
+
_requestRange(r) {
|
|
1745
|
+
if (this.syncsProcessing > 0) return false
|
|
1589
1746
|
|
|
1590
|
-
|
|
1591
|
-
// should be way less than this, but this is worst case upper bound for the skiplist
|
|
1592
|
-
let tries = this.inflight
|
|
1747
|
+
const { length, fork } = this.core.state
|
|
1593
1748
|
|
|
1594
|
-
|
|
1595
|
-
i =
|
|
1596
|
-
|
|
1749
|
+
if (r.blocks) {
|
|
1750
|
+
for (let i = r.start; i < r.end; i++) {
|
|
1751
|
+
const index = r.blocks[i]
|
|
1752
|
+
const has = index < this._remoteContiguousLength || this.missingBlocks.get(index) === true
|
|
1753
|
+
if (has === true && this._requestRangeBlock(index, length)) return true
|
|
1754
|
+
this._maybeWant(index, r)
|
|
1755
|
+
}
|
|
1597
1756
|
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
} while (tries-- > 0)
|
|
1757
|
+
return false
|
|
1758
|
+
}
|
|
1601
1759
|
|
|
1602
|
-
|
|
1760
|
+
this._populateRangeBatches(r)
|
|
1603
1761
|
|
|
1604
|
-
|
|
1605
|
-
i = this._findNext(i)
|
|
1606
|
-
if (i === -1 || i >= off) break
|
|
1762
|
+
const end = this._getValidEnd(r.end)
|
|
1607
1763
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1764
|
+
if (end <= r.start || fork !== this.remoteFork) return false
|
|
1765
|
+
|
|
1766
|
+
for (let i = 0; i < r.batches.length; i++) {
|
|
1767
|
+
const b = r.batches[i]
|
|
1768
|
+
|
|
1769
|
+
const batchStart = Math.max(b.batch * WANT_BATCH, r.start)
|
|
1770
|
+
const batchEnd = Math.min((b.batch + 1) * WANT_BATCH, end)
|
|
1771
|
+
const len = batchEnd - batchStart
|
|
1772
|
+
const off = batchStart + (r.linear ? 0 : Math.floor(Math.random() * len))
|
|
1773
|
+
|
|
1774
|
+
if (
|
|
1775
|
+
this._requestRangeBatch(off, end, length) ||
|
|
1776
|
+
this._requestRangeBatch(batchStart, off, length)
|
|
1777
|
+
) {
|
|
1778
|
+
return true
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
this._maybeWant(batchStart, b)
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// Here we should consider making an OOB request (ie if the remote says they have a newer block)
|
|
1785
|
+
// but the upgrade code already does that, so no big deal for now
|
|
1611
1786
|
|
|
1612
|
-
this._maybeWant(r.start, len)
|
|
1613
1787
|
return false
|
|
1614
1788
|
}
|
|
1615
1789
|
|
|
@@ -1650,28 +1824,10 @@ class Peer {
|
|
|
1650
1824
|
return true
|
|
1651
1825
|
}
|
|
1652
1826
|
|
|
1653
|
-
this.
|
|
1827
|
+
this._maybeWantAnyRange(f.batch.want.start, len, f)
|
|
1654
1828
|
return false
|
|
1655
1829
|
}
|
|
1656
1830
|
|
|
1657
|
-
_maybeWant(start, length = 1) {
|
|
1658
|
-
if (start + length <= this.remoteContiguousLength) return
|
|
1659
|
-
|
|
1660
|
-
let i = Math.floor(start / DEFAULT_SEGMENT_SIZE)
|
|
1661
|
-
const n = Math.ceil((start + length) / DEFAULT_SEGMENT_SIZE)
|
|
1662
|
-
|
|
1663
|
-
for (; i < n; i++) {
|
|
1664
|
-
if (this.segmentsWanted.has(i)) continue
|
|
1665
|
-
this.segmentsWanted.add(i)
|
|
1666
|
-
|
|
1667
|
-
this.wireWant.send({
|
|
1668
|
-
start: i * DEFAULT_SEGMENT_SIZE,
|
|
1669
|
-
length: DEFAULT_SEGMENT_SIZE
|
|
1670
|
-
})
|
|
1671
|
-
incrementTx(this.stats.wireWant, this.replicator.stats.wireWant)
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
1831
|
isActive() {
|
|
1676
1832
|
if (this.paused || this.removed || this.core.header.frozen) return false
|
|
1677
1833
|
return true
|
|
@@ -1758,6 +1914,7 @@ module.exports = class Replicator {
|
|
|
1758
1914
|
wireCancel: { tx: 0, rx: 0 },
|
|
1759
1915
|
wireData: { tx: 0, rx: 0 },
|
|
1760
1916
|
wireWant: { tx: 0, rx: 0 },
|
|
1917
|
+
wireUnwant: { tx: 0, rx: 0 },
|
|
1761
1918
|
wireBitfield: { tx: 0, rx: 0 },
|
|
1762
1919
|
wireRange: { tx: 0, rx: 0 },
|
|
1763
1920
|
wireExtension: { tx: 0, rx: 0 },
|
|
@@ -2159,11 +2316,7 @@ module.exports = class Replicator {
|
|
|
2159
2316
|
if (f.fork === fork) return f
|
|
2160
2317
|
}
|
|
2161
2318
|
|
|
2162
|
-
const f =
|
|
2163
|
-
fork,
|
|
2164
|
-
inflight: [],
|
|
2165
|
-
batch: null
|
|
2166
|
-
}
|
|
2319
|
+
const f = new ForkRequest(this, fork)
|
|
2167
2320
|
|
|
2168
2321
|
this._reorgs.push(f)
|
|
2169
2322
|
|
|
@@ -2209,6 +2362,7 @@ module.exports = class Replicator {
|
|
|
2209
2362
|
|
|
2210
2363
|
_removePeer(peer) {
|
|
2211
2364
|
this.peers.splice(this.peers.indexOf(peer), 1)
|
|
2365
|
+
peer.wants.destroy()
|
|
2212
2366
|
|
|
2213
2367
|
if (this._manifestPeer === peer) this._manifestPeer = null
|
|
2214
2368
|
|
|
@@ -2549,12 +2703,12 @@ module.exports = class Replicator {
|
|
|
2549
2703
|
this.updatePeer(peer)
|
|
2550
2704
|
}
|
|
2551
2705
|
|
|
2552
|
-
_onwant(peer, start, length) {
|
|
2706
|
+
_onwant(peer, start, length, any) {
|
|
2553
2707
|
if (!peer.isActive()) return
|
|
2554
2708
|
|
|
2555
2709
|
const contig = Math.min(this.core.state.length, this.core.header.hints.contiguousLength)
|
|
2556
2710
|
|
|
2557
|
-
if (start + length < contig || this.core.state.length === contig) {
|
|
2711
|
+
if (start + length < contig || this.core.state.length === contig || (any && start < contig)) {
|
|
2558
2712
|
peer.wireRange.send({
|
|
2559
2713
|
drop: false,
|
|
2560
2714
|
start: 0,
|
|
@@ -2566,14 +2720,22 @@ module.exports = class Replicator {
|
|
|
2566
2720
|
|
|
2567
2721
|
length = Math.min(length, this.core.state.length - start)
|
|
2568
2722
|
|
|
2569
|
-
|
|
2723
|
+
if (any) {
|
|
2724
|
+
const bit = this.core.bitfield.firstSet(start)
|
|
2725
|
+
if (bit > -1 && bit < start + length) {
|
|
2726
|
+
peer.wireRange.send({
|
|
2727
|
+
drop: false,
|
|
2728
|
+
start: bit,
|
|
2729
|
+
length: 1
|
|
2730
|
+
})
|
|
2731
|
+
}
|
|
2732
|
+
return
|
|
2733
|
+
}
|
|
2570
2734
|
|
|
2571
2735
|
for (const msg of this.core.bitfield.want(start, length)) {
|
|
2572
2736
|
peer.wireBitfield.send(msg)
|
|
2573
2737
|
incrementTx(peer.stats.wireBitfield, this.stats.wireBitfield)
|
|
2574
2738
|
}
|
|
2575
|
-
|
|
2576
|
-
peer.protomux.uncork()
|
|
2577
2739
|
}
|
|
2578
2740
|
|
|
2579
2741
|
async _onreorgdata(peer, req, data) {
|
|
@@ -2613,6 +2775,7 @@ module.exports = class Replicator {
|
|
|
2613
2775
|
// should investigate the complexity in going the other way
|
|
2614
2776
|
|
|
2615
2777
|
const u = this._upgrade
|
|
2778
|
+
const old = this._reorgs
|
|
2616
2779
|
|
|
2617
2780
|
this._reorgs = [] // clear all as the nodes are against the old tree - easier
|
|
2618
2781
|
this._applyingReorg = this.core.reorg(f.batch, null) // TODO: null should be the first/last peer?
|
|
@@ -2638,6 +2801,9 @@ module.exports = class Replicator {
|
|
|
2638
2801
|
r.end = r.userEnd
|
|
2639
2802
|
}
|
|
2640
2803
|
|
|
2804
|
+
for (const f of old) f.resolve()
|
|
2805
|
+
f.resolve()
|
|
2806
|
+
|
|
2641
2807
|
this.updateAll()
|
|
2642
2808
|
}
|
|
2643
2809
|
|
package/lib/wants.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
const BATCH_UINTS = 128
|
|
2
|
+
const BATCH_BYTES = BATCH_UINTS * 4
|
|
3
|
+
const BATCH = BATCH_BYTES * 8 // in bits
|
|
4
|
+
const MAX_REMOTE_BATCHES = 4
|
|
5
|
+
const MAX_RANGE = 8 * 256 * 1024
|
|
6
|
+
const MIN_RANGE = 32 // 4bit ints
|
|
7
|
+
const MAX = 512
|
|
8
|
+
const MAX_ANY = 16
|
|
9
|
+
|
|
10
|
+
class LocalWants {
|
|
11
|
+
constructor(peer) {
|
|
12
|
+
this.destroyed = false
|
|
13
|
+
this.peer = peer
|
|
14
|
+
this.wants = new Map()
|
|
15
|
+
this.free = new Set()
|
|
16
|
+
this.any = null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
add(index, handle) {
|
|
20
|
+
const b = (index - (index & (BATCH - 1))) / BATCH
|
|
21
|
+
return this.addBatch(b, handle)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
addAnyRange(start, length, handle) {
|
|
25
|
+
if (this.destroyed) return null
|
|
26
|
+
if (this.any === null) this.any = []
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < this.any.length; i++) {
|
|
29
|
+
const w = this.any[i]
|
|
30
|
+
if (w.start === start && w.length === length) {
|
|
31
|
+
w.handles.add(handle)
|
|
32
|
+
handle.addWant(w)
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const w = { wants: this, start, length, any: true, handles: new Set([handle]) }
|
|
38
|
+
this.any.push(w)
|
|
39
|
+
handle.addWant(w)
|
|
40
|
+
|
|
41
|
+
return { start, length, any: true }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
removeAnyRange(start, length, handle) {
|
|
45
|
+
if (this.any === null) return null
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < this.any.length; i++) {
|
|
48
|
+
const w = this.any[i]
|
|
49
|
+
if (w.start !== start || w.length !== length) continue
|
|
50
|
+
|
|
51
|
+
w.handles.delete(handle)
|
|
52
|
+
handle.removeWant(w)
|
|
53
|
+
if (w.handles.size > 0) return null
|
|
54
|
+
|
|
55
|
+
const head = this.any.pop()
|
|
56
|
+
if (head !== w) this.any[i] = head
|
|
57
|
+
return { start, length, any: true }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
addBatch(index, handle) {
|
|
64
|
+
if (this.destroyed) return null
|
|
65
|
+
let w = this.wants.get(index)
|
|
66
|
+
|
|
67
|
+
if (w) {
|
|
68
|
+
const size = w.handles.size
|
|
69
|
+
w.handles.add(handle)
|
|
70
|
+
if (w.handles.size !== size) {
|
|
71
|
+
handle.addWant(w)
|
|
72
|
+
}
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// start here is the batch number for simplicity....
|
|
77
|
+
w = { wants: this, start: index, length: 0, any: false, handles: new Set([handle]) }
|
|
78
|
+
|
|
79
|
+
if (this.free.has(index)) {
|
|
80
|
+
this.free.delete(index)
|
|
81
|
+
this.wants.set(index, w)
|
|
82
|
+
handle.addWant(w)
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let unwant = null
|
|
87
|
+
if (this.wants.size + this.free.size === MAX) {
|
|
88
|
+
if (this.free.size === 0) {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
unwant = this._unwant()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.wants.set(index, w)
|
|
95
|
+
handle.addWant(w)
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
want: { start: index * BATCH, length: BATCH, any: false },
|
|
99
|
+
unwant
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
remove(index, handle) {
|
|
104
|
+
const b = (index - (index & (BATCH - 1))) / BATCH
|
|
105
|
+
return this.removeBatch(b, handle)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
removeBatch(index, handle) {
|
|
109
|
+
if (this.destroyed) return false
|
|
110
|
+
|
|
111
|
+
const w = this.wants.get(index)
|
|
112
|
+
if (!w) {
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
w.handles.delete(handle)
|
|
117
|
+
handle.removeWant(w)
|
|
118
|
+
|
|
119
|
+
if (w.handles.size === 0) {
|
|
120
|
+
this.free.add(index)
|
|
121
|
+
this.wants.delete(index)
|
|
122
|
+
return this.free.size === 1 && this.wants.size === MAX - 1
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
destroy() {
|
|
129
|
+
if (this.destroyed) return
|
|
130
|
+
this.destroyed = true
|
|
131
|
+
|
|
132
|
+
for (const w of this.wants.values()) {
|
|
133
|
+
for (const handle of w.handles) {
|
|
134
|
+
handle.removeWant(w)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.wants.clear()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_unwant() {
|
|
142
|
+
for (const index of this.free) {
|
|
143
|
+
this.free.delete(index)
|
|
144
|
+
return { start: index * BATCH, length: BATCH, any: false }
|
|
145
|
+
}
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
class RemoteWants {
|
|
151
|
+
constructor() {
|
|
152
|
+
this.any = null
|
|
153
|
+
this.all = false
|
|
154
|
+
this.size = 0
|
|
155
|
+
this.batches = []
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
hasAny(start, length) {
|
|
159
|
+
for (let i = 0; i < this.any.length; i++) {
|
|
160
|
+
const a = this.any[i]
|
|
161
|
+
const e = start + length
|
|
162
|
+
const end = a.start + a.length
|
|
163
|
+
|
|
164
|
+
if (a.start <= start && start < end) return true
|
|
165
|
+
if (a.start < e && e <= end) return true
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return this.all
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
hasRange(start, length) {
|
|
172
|
+
if (this.any !== null && this.hasAny(start, length)) return true
|
|
173
|
+
if (length === 1) return this.has(start)
|
|
174
|
+
|
|
175
|
+
let smallest = -1
|
|
176
|
+
|
|
177
|
+
for (let i = 0; i < this.batches.length; i++) {
|
|
178
|
+
const b = this.batches[i]
|
|
179
|
+
if (b.length < smallest || smallest === -1) smallest = b.length
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (smallest === -1) return this.all
|
|
183
|
+
|
|
184
|
+
const r = start & (smallest - 1)
|
|
185
|
+
const end = start + length
|
|
186
|
+
|
|
187
|
+
let max = 3
|
|
188
|
+
|
|
189
|
+
for (let i = start - r; i < end; i += smallest) {
|
|
190
|
+
if (max-- === 0) return true // just to save work
|
|
191
|
+
if (this.has(i)) return true
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return this.all
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
has(index) {
|
|
198
|
+
for (let i = 0; i < this.batches.length; i++) {
|
|
199
|
+
const b = this.batches[i]
|
|
200
|
+
const block = (index - (index & (b.length - 1))) / b.length
|
|
201
|
+
if (b.ranges.has(block)) return true
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return this.all
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
add(range) {
|
|
208
|
+
if (range.any) {
|
|
209
|
+
if (this.any === null) this.any = []
|
|
210
|
+
if (this.any.length < MAX_ANY) this.any.push(range)
|
|
211
|
+
else this.all = true
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (range.length > MAX_RANGE) return false
|
|
216
|
+
|
|
217
|
+
if (!validateBatchRange(range)) {
|
|
218
|
+
this.all = true
|
|
219
|
+
return true
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (this.size >= MAX) {
|
|
223
|
+
this.all = true
|
|
224
|
+
return true
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (let i = 0; i < this.batches.length; i++) {
|
|
228
|
+
const b = this.batches[i]
|
|
229
|
+
if (b.length === range.length) {
|
|
230
|
+
b.ranges.add(range.start / range.length)
|
|
231
|
+
this.size++
|
|
232
|
+
return true
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (this.batches.length >= MAX_REMOTE_BATCHES) {
|
|
237
|
+
this.all = true
|
|
238
|
+
return true
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.batches.push({
|
|
242
|
+
length: range.length,
|
|
243
|
+
ranges: new Set([range.start / range.length])
|
|
244
|
+
})
|
|
245
|
+
this.size++
|
|
246
|
+
|
|
247
|
+
return true
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
remove(range) {
|
|
251
|
+
if (range.any) {
|
|
252
|
+
if (this.any === null) return false
|
|
253
|
+
|
|
254
|
+
for (let i = 0; i < this.any.length; i++) {
|
|
255
|
+
const a = this.any[i]
|
|
256
|
+
|
|
257
|
+
if (a.start === range.start && a.length === range.length) {
|
|
258
|
+
const head = this.any.pop()
|
|
259
|
+
if (head !== a) this.any[i] = head
|
|
260
|
+
return true
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return false
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!validateBatchRange(range)) return false
|
|
268
|
+
|
|
269
|
+
for (let i = 0; i < this.batches.length; i++) {
|
|
270
|
+
const b = this.batches[i]
|
|
271
|
+
if (b.length !== range.length) continue
|
|
272
|
+
|
|
273
|
+
const size = b.ranges.size
|
|
274
|
+
b.ranges.delete(range.start / range.length)
|
|
275
|
+
if (b.ranges.size !== size) this.size--
|
|
276
|
+
|
|
277
|
+
if (b.ranges.size === 0) {
|
|
278
|
+
const head = this.batches.pop()
|
|
279
|
+
if (head !== b) this.batches[i] = head
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return true
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return false
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
exports.LocalWants = LocalWants
|
|
290
|
+
exports.RemoteWants = RemoteWants
|
|
291
|
+
exports.WANT_BATCH = BATCH
|
|
292
|
+
|
|
293
|
+
function validateBatchRange(range) {
|
|
294
|
+
if (range.length > MAX_RANGE || range.length < MIN_RANGE) {
|
|
295
|
+
return false
|
|
296
|
+
}
|
|
297
|
+
// check if power of two
|
|
298
|
+
if ((range.length & (range.length - 1)) !== 0) {
|
|
299
|
+
return false
|
|
300
|
+
}
|
|
301
|
+
// start must be a multiple of the length
|
|
302
|
+
if (range.start & (range.length - 1)) {
|
|
303
|
+
return false
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return true
|
|
307
|
+
}
|