trac-msb 0.2.26 → 0.2.28

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.
@@ -0,0 +1,434 @@
1
+ // Exact-text patch set for hypercore 11.18.3 lib/replicator.js (OPENMAYHEM-STALLFIX v2).
2
+ // Each edit is [original, replacement]; the original must occur exactly once in the unpatched file.
3
+ // Keep this file free of backticks and dollar-brace sequences inside the edit texts (String.raw templates).
4
+
5
+ // Upstream c24f37a6 (#911): a close event that already fired cannot detach a later attachment.
6
+ const closedStreamGuard = [
7
+ String.raw` attachTo(protomux) {
8
+ if (this.core.closed) return
9
+ `,
10
+ String.raw` attachTo(protomux) {
11
+ if (this.core.closed) return
12
+ if (protomux.stream.destroying) return
13
+ `
14
+ ]
15
+
16
+ export default {
17
+ version: '11.18.3',
18
+ file: 'lib/replicator.js',
19
+ originalSha256: '2e7a2631a0d8e805861339bbc75e6f6de75565813cf201256105408c53c8ac93',
20
+ patchedSha256: '1ba2f169d20840d41333cc55e8fbb7da9bc5b4698d808739ad97acce0cbe0f1a',
21
+ // Only this exact released v1 file is eligible for an in-place upgrade.
22
+ previousPatchedSha256: '79944caa33cc875d563f7eba8b820ffbd9323984d30e2fd6c58030804ded2f6c',
23
+ upgradeEdits: [
24
+ ['// OPENMAYHEM-STALLFIX v1 (hypercore 11.18.3).', '// OPENMAYHEM-STALLFIX v2 (hypercore 11.18.3).'],
25
+ ["const STALLFIX = 'openmayhem-stallfix-v1'", "const STALLFIX = 'openmayhem-stallfix-v2'"],
26
+ closedStreamGuard
27
+ ],
28
+ edits: [
29
+ // 1. Constants + marker.
30
+ [
31
+ String.raw`const MAX_BACKOFFS = MAX_INVALID_REQUESTS / 2
32
+ `,
33
+ String.raw`const MAX_BACKOFFS = MAX_INVALID_REQUESTS / 2
34
+
35
+ // OPENMAYHEM-STALLFIX v2 (hypercore 11.18.3). Install-time patch shipped by trac-msb and trac-peer
36
+ // (scripts/hypercore-stallfix/). Fixes the waiting-side replication stall: an in-flight request that a peer
37
+ // never answers is cancelled after STALLFIX_REQUEST_MAX_AGE and asked elsewhere; that peer gets a cool-down;
38
+ // a peer we paused gets a capped NOT_AVAILABLE instead of silence; an INVALID_OPERATION verification error pauses
39
+ // a peer for an escalating time instead of until disconnect. Wire format unchanged.
40
+ const STALLFIX = 'openmayhem-stallfix-v2'
41
+ const STALLFIX_REQUEST_MAX_AGE = 30000 // ms an in-flight request may wait for its answer
42
+ const STALLFIX_SWEEP_INTERVAL = 5000 // one unref'd timer per replicator, only while requests are in flight
43
+ const STALLFIX_MAX_SCAN = 4096 // request slots looked at per sweep
44
+ const STALLFIX_MAX_EXPIRE = 64 // requests expired per sweep
45
+ const STALLFIX_COOLDOWN_MIN = 30000 // first cool-down of a peer that let a request expire (doubles, capped)
46
+ const STALLFIX_COOLDOWN_MAX = 240000
47
+ const STALLFIX_NOT_AVAILABLE_WINDOW = 60000
48
+ const STALLFIX_NOT_AVAILABLE_MAX = 16 // NOT_AVAILABLE replies per window to a peer we paused; silent beyond
49
+ const STALLFIX_OP_PAUSE_MIN = 60000 // first timed pause after an INVALID_OPERATION verification error (doubles)
50
+ const STALLFIX_OP_PAUSE_EPISODES = 5 // timed pauses without valid data in between; the next one is permanent
51
+ `
52
+ ],
53
+ // 2. Per-peer state.
54
+ [
55
+ String.raw` this.lastExtensionRecv = ''
56
+
57
+ replicator._ifAvailable++
58
+ replicator._active++
59
+ }
60
+ `,
61
+ String.raw` this.lastExtensionRecv = ''
62
+
63
+ this._sfTimeouts = 0 // OPENMAYHEM-STALLFIX: cool-down level of this peer
64
+ this._sfCoolUntil = 0
65
+ this._sfNaWindow = 0
66
+ this._sfNaCount = 0
67
+ this._sfOpUntil = 0 // timed pause after INVALID_OPERATION (0 = none)
68
+ this._sfOpLevel = 0
69
+
70
+ replicator._ifAvailable++
71
+ replicator._active++
72
+ }
73
+ `
74
+ ],
75
+ // 3. Responder: reply NOT_AVAILABLE (capped) to a peer we paused instead of dropping the request silently.
76
+ // Also covers proof === null for an inactive peer (upstream 03f7156: that case threw a TypeError here).
77
+ [
78
+ String.raw` if (!this.isActive() && proof.block !== null) {
79
+ return
80
+ }
81
+ `,
82
+ String.raw` if (!this.isActive() && (proof === null || proof.block !== null)) {
83
+ // OPENMAYHEM-STALLFIX: upstream returns silently here and the remote waits forever. Never INVALID_REQUEST.
84
+ this._sfNotAvailable(req.msg.id)
85
+ return
86
+ }
87
+ `
88
+ ],
89
+ // 3b. INVALID_OPERATION during verification: timed pause instead of paused until disconnect. On 11.18.3 this
90
+ // error is also raised for VALID replies whose upgrade starts at a length we no longer have (local commit
91
+ // or another upgrade moved our length while the request was in flight; upstream 11.40.0 #895 refits such
92
+ // proofs). Other verification errors (possible forks, bad data) keep the conflict check and the permanent pause.
93
+ [
94
+ String.raw` if (err.code !== 'INVALID_OPERATION') {
95
+ // might be a fork, verify
96
+ this._checkIfConflict()
97
+ }
98
+
99
+ this.paused = true
100
+ this.replicator._onnodata(this, req, 0)
101
+ `,
102
+ String.raw` if (err.code !== 'INVALID_OPERATION') {
103
+ // might be a fork, verify
104
+ this._checkIfConflict()
105
+ this.paused = true
106
+ } else {
107
+ // OPENMAYHEM-STALLFIX: never a fork (no conflict check); pause for a bounded, escalating time
108
+ this._sfInvalidOperationPause(Date.now())
109
+ }
110
+
111
+ this.replicator._onnodata(this, req, 0)
112
+ `
113
+ ],
114
+ // 3c. A timed pause makes the peer inactive like a pause, until it expires.
115
+ [
116
+ String.raw` isActive() {
117
+ if (this.paused || this.removed || this.core.header.frozen) return false
118
+ return true
119
+ }
120
+ `,
121
+ String.raw` isActive() {
122
+ if (this.paused || this.removed || this.core.header.frozen) return false
123
+ if (this._sfOpUntil !== 0 && this._sfOpUntil > Date.now()) return false // OPENMAYHEM-STALLFIX timed pause
124
+ return true
125
+ }
126
+ `
127
+ ],
128
+ // 4. Peer helpers: capped NOT_AVAILABLE and request expiry.
129
+ [
130
+ String.raw` _cancelRequest(req) {
131
+ if (req.priority === PRIORITY.CANCELLED) return
132
+ `,
133
+ String.raw` // OPENMAYHEM-STALLFIX: at most STALLFIX_NOT_AVAILABLE_MAX replies per window, silent beyond the cap.
134
+ _sfNotAvailable(id) {
135
+ if (this.removed === true) return
136
+ const now = Date.now()
137
+ if (now - this._sfNaWindow >= STALLFIX_NOT_AVAILABLE_WINDOW) {
138
+ this._sfNaWindow = now
139
+ this._sfNaCount = 0
140
+ }
141
+ if (this._sfNaCount >= STALLFIX_NOT_AVAILABLE_MAX) {
142
+ this.replicator._sfStats.notAvailableSuppressed++
143
+ return
144
+ }
145
+ this._sfNaCount++
146
+ this.replicator._sfStats.notAvailableSent++
147
+ this.wireNoData.send({ request: id, reason: NOT_AVAILABLE })
148
+ }
149
+
150
+ // OPENMAYHEM-STALLFIX: timed pause after INVALID_OPERATION. Replies already in flight do not extend it.
151
+ // Episodes double (60 s .. 16 min); valid block data from the peer resets the level (_ondata); after
152
+ // STALLFIX_OP_PAUSE_EPISODES episodes without valid data the peer is paused until disconnect, as upstream.
153
+ _sfInvalidOperationPause(now) {
154
+ if (this.paused === true || this._sfOpUntil > now) return
155
+ if (this._sfOpLevel >= STALLFIX_OP_PAUSE_EPISODES) {
156
+ this._sfOpUntil = 0
157
+ this.paused = true
158
+ this.replicator._sfStats.invalidOperationPermanent++
159
+ return
160
+ }
161
+ this._sfOpLevel++
162
+ this._sfOpUntil = now + this.replicator._sfOpPauseMin * 2 ** (this._sfOpLevel - 1)
163
+ this.replicator._sfStats.invalidOperationPauses++
164
+ this.replicator._sfArm()
165
+ }
166
+
167
+ // OPENMAYHEM-STALLFIX: give up on one request this peer did not answer in time. Bookkeeping as for a
168
+ // NOT_AVAILABLE reply (_clearRequest re-queues a wanted block) plus a wire cancel. The id is only reused
169
+ // after the next roundtrip (as in _cancelRequest), so a late reply finds no request and is dropped
170
+ // in ondata/onnodata: it is not invalid data and does not pause the peer.
171
+ _sfExpireRequest(req, now) {
172
+ if (req.priority === PRIORITY.CANCELLED) return false
173
+ req.priority = PRIORITY.CANCELLED
174
+
175
+ this.inflight--
176
+ this.replicator._requestDone(req.id, false)
177
+
178
+ if (this.roundtripQueue === null) this.roundtripQueue = new RoundtripQueue()
179
+ this.roundtripQueue.add(req.id)
180
+ if (this.removed === false) {
181
+ this.wireCancel.send({ request: req.id })
182
+ incrementTx(this.stats.wireCancel, this.replicator.stats.wireCancel)
183
+ }
184
+
185
+ if (this._sfCoolUntil <= now) {
186
+ if (this._sfTimeouts < 8) this._sfTimeouts++
187
+ const cooldown = this.replicator._sfCooldownMin * 2 ** (this._sfTimeouts - 1)
188
+ this._sfCoolUntil = now + Math.min(STALLFIX_COOLDOWN_MAX, cooldown)
189
+ }
190
+
191
+ if (this.replicator._manifestPeer === this && this.core.header.manifest === null) {
192
+ this.replicator._manifestPeer = null
193
+ }
194
+
195
+ this.replicator._clearRequest(this, req)
196
+ return true
197
+ }
198
+
199
+ _cancelRequest(req) {
200
+ if (req.priority === PRIORITY.CANCELLED) return
201
+ `
202
+ ],
203
+ // 5. Arm the sweep when a request goes in flight.
204
+ [
205
+ String.raw` this.inflight++
206
+ this.replicator._inflight.add(req)
207
+ `,
208
+ String.raw` this.inflight++
209
+ this.replicator._inflight.add(req)
210
+ if (this.replicator._sfTimer === null) this.replicator._sfArm() // OPENMAYHEM-STALLFIX
211
+ `
212
+ ],
213
+ // 6. Marker on the Replicator class (runtime check: core.replicator.constructor.STALLFIX).
214
+ [
215
+ String.raw` static Peer = Peer // hack to be able to access Peer from outside this module
216
+ `,
217
+ String.raw` static Peer = Peer // hack to be able to access Peer from outside this module
218
+ static STALLFIX = STALLFIX // OPENMAYHEM-STALLFIX marker
219
+ `
220
+ ],
221
+ // 7. Replicator state.
222
+ [
223
+ String.raw` this._notDownloadingLinger = notDownloadingLinger
224
+ this._downloadingTimer = null
225
+ `,
226
+ String.raw` this._notDownloadingLinger = notDownloadingLinger
227
+ this._downloadingTimer = null
228
+
229
+ // OPENMAYHEM-STALLFIX state (the _sf* numbers are only changed by tests)
230
+ this._sfTimer = null
231
+ this._sfCursor = 0
232
+ this._sfAllCooling = false
233
+ this._sfMaxAge = STALLFIX_REQUEST_MAX_AGE
234
+ this._sfInterval = STALLFIX_SWEEP_INTERVAL
235
+ this._sfCooldownMin = STALLFIX_COOLDOWN_MIN
236
+ this._sfOpPauseMin = STALLFIX_OP_PAUSE_MIN
237
+ this._sfStats = {
238
+ expired: 0,
239
+ notAvailableSent: 0,
240
+ notAvailableSuppressed: 0,
241
+ invalidOperationPauses: 0,
242
+ invalidOperationPermanent: 0
243
+ }
244
+ `
245
+ ],
246
+ // 8. Valid block data from a peer ends its cool-down.
247
+ [
248
+ String.raw` _ondata(peer, req, data) {
249
+ req.elapsed = Date.now() - req.timestamp
250
+ if (data.block !== null) {
251
+ `,
252
+ String.raw` _ondata(peer, req, data) {
253
+ req.elapsed = Date.now() - req.timestamp
254
+ if (data.block !== null) {
255
+ peer._sfTimeouts = 0 // OPENMAYHEM-STALLFIX
256
+ peer._sfCoolUntil = 0
257
+ peer._sfOpLevel = 0
258
+ `
259
+ ],
260
+ // 9. The sweep (bounded work, one timer per replicator).
261
+ [
262
+ String.raw` _maybeUpdate() {
263
+ return this._upgrade !== null && this._upgrade.inflight.length === 0
264
+ }
265
+ `,
266
+ String.raw` _maybeUpdate() {
267
+ return this._upgrade !== null && this._upgrade.inflight.length === 0
268
+ }
269
+
270
+ // OPENMAYHEM-STALLFIX: one unref'd timer per replicator, armed by _send, re-armed only while requests are
271
+ // in flight or a peer is cooling down. Looks at <= STALLFIX_MAX_SCAN slots, expires <= STALLFIX_MAX_EXPIRE.
272
+ _sfArm() {
273
+ if (this.destroyed || this._sfTimer !== null) return
274
+ this._sfTimer = setTimeout(sfSweepLater, this._sfInterval, this)
275
+ if (this._sfTimer.unref) this._sfTimer.unref()
276
+ }
277
+
278
+ _sfSweep() {
279
+ this._sfTimer = null
280
+ if (this.destroyed) return
281
+
282
+ const now = Date.now()
283
+ const reqs = this._inflight._requests
284
+ const scan = Math.min(reqs.length, STALLFIX_MAX_SCAN)
285
+ let live = 0
286
+ let expired = 0
287
+
288
+ for (let i = 0; i < scan && expired < STALLFIX_MAX_EXPIRE; i++) {
289
+ if (this._sfCursor >= reqs.length) this._sfCursor = 0
290
+ const req = reqs[this._sfCursor++]
291
+ if (!req) continue
292
+ live++
293
+ if (now - req.timestamp < this._sfMaxAge) continue
294
+ if (req.peer._sfExpireRequest(req, now) === true) expired++
295
+ }
296
+
297
+ let cooling = 0
298
+ let usable = 0
299
+ let opPaused = 0
300
+ let cooled = false
301
+ for (const peer of this.peers) {
302
+ if (peer._sfCoolUntil !== 0 && peer._sfCoolUntil <= now) {
303
+ peer._sfCoolUntil = 0
304
+ cooled = true
305
+ }
306
+ if (peer._sfOpUntil !== 0) {
307
+ if (peer._sfOpUntil <= now) {
308
+ peer._sfOpUntil = 0
309
+ cooled = true
310
+ } else {
311
+ opPaused++
312
+ }
313
+ }
314
+ if (peer._sfCoolUntil !== 0) cooling++
315
+ else if (peer.isActive()) usable++
316
+ }
317
+ // if no other peer is usable, a cooling peer may still be asked (no deadlock with a single peer)
318
+ this._sfAllCooling = cooling > 0 && usable === 0
319
+
320
+ if (expired > 0) this._sfStats.expired += expired
321
+ if (expired > 0 || cooled) this.updateAll()
322
+
323
+ if (live > 0 || cooling > 0 || opPaused > 0 || scan < reqs.length) this._sfArm()
324
+ }
325
+
326
+ _sfCoolingDown(peer) {
327
+ return peer._sfCoolUntil !== 0 && this._sfAllCooling === false && peer._sfCoolUntil > Date.now()
328
+ }
329
+ `
330
+ ],
331
+ // 10. Hotswap does not pick a cooling peer.
332
+ [
333
+ String.raw` if (!peer.isActive() || peer.inflight >= maxHotswaps) return
334
+ `,
335
+ String.raw` if (!peer.isActive() || peer.inflight >= maxHotswaps || this._sfCoolingDown(peer)) return
336
+ `
337
+ ],
338
+ // 11. _updatePeer: skip a cooling peer + backport upstream 8cf8ad9 fix 2 (stale queue entry must not
339
+ // consume the peer's update).
340
+ [
341
+ String.raw` _updatePeer(peer) {
342
+ if (!peer.isActive() || peer.inflight >= peer.getMaxInflight() || !peer.remoteUploading) {
343
+ return false
344
+ }
345
+ `,
346
+ String.raw` _updatePeer(peer) {
347
+ if (
348
+ !peer.isActive() ||
349
+ peer.inflight >= peer.getMaxInflight() ||
350
+ !peer.remoteUploading ||
351
+ this._sfCoolingDown(peer)
352
+ ) {
353
+ return false
354
+ }
355
+ `
356
+ ],
357
+ [
358
+ String.raw` for (const b of blks) {
359
+ if (b.queued === false || peer._requestBlock(b) === true) {
360
+ b.queued = false
361
+ blks.dequeue()
362
+ return true
363
+ }
364
+ }
365
+ `,
366
+ String.raw` for (const b of blks) {
367
+ if (b.queued === false) {
368
+ // upstream 8cf8ad9 (#896) fix 2, backported by OPENMAYHEM-STALLFIX
369
+ blks.dequeue()
370
+ continue
371
+ }
372
+ if (peer._requestBlock(b) === true) {
373
+ b.queued = false
374
+ blks.dequeue()
375
+ return true
376
+ }
377
+ }
378
+ `
379
+ ],
380
+ // 12. _updatePeerNonPrimary: skip a cooling peer.
381
+ [
382
+ String.raw` _updatePeerNonPrimary(peer) {
383
+ if (!peer.isActive() || peer.inflight >= peer.getMaxInflight() || !peer.remoteUploading) {
384
+ return false
385
+ }
386
+ `,
387
+ String.raw` _updatePeerNonPrimary(peer) {
388
+ if (
389
+ !peer.isActive() ||
390
+ peer.inflight >= peer.getMaxInflight() ||
391
+ !peer.remoteUploading ||
392
+ this._sfCoolingDown(peer)
393
+ ) {
394
+ return false
395
+ }
396
+ `
397
+ ],
398
+ // 13. destroy() clears the sweep timer.
399
+ [
400
+ String.raw` if (this._downloadingTimer) {
401
+ clearTimeout(this._downloadingTimer)
402
+ this._downloadingTimer = null
403
+ }
404
+
405
+ while (this.peers.length) {
406
+ `,
407
+ String.raw` if (this._downloadingTimer) {
408
+ clearTimeout(this._downloadingTimer)
409
+ this._downloadingTimer = null
410
+ }
411
+
412
+ if (this._sfTimer !== null) {
413
+ clearTimeout(this._sfTimer) // OPENMAYHEM-STALLFIX
414
+ this._sfTimer = null
415
+ }
416
+
417
+ while (this.peers.length) {
418
+ `
419
+ ],
420
+ // 14. Timer trampoline.
421
+ [
422
+ String.raw`function setDownloadingLater(repl, downloading, session) {
423
+ `,
424
+ String.raw`function sfSweepLater(repl) {
425
+ repl._sfSweep() // OPENMAYHEM-STALLFIX
426
+ }
427
+
428
+ function setDownloadingLater(repl, downloading, session) {
429
+ `
430
+ ],
431
+ // Upstream #911, in addition to the existing stall fixes.
432
+ closedStreamGuard
433
+ ]
434
+ }