@blockcast/mmt-container 2.2.0-main.75b39a2cdddf → 2.2.0-main.7e8a9d13c01c
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/dist/browser-runtime.js +634 -129
- package/dist/browser-runtime.js.map +3 -3
- package/dist/delta-fallback-config.js.map +1 -1
- package/dist/fec-manager.d.ts +37 -11
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +181 -52
- package/dist/fec-manager.js.map +2 -2
- package/dist/fec-worker-setup.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +2 -2
- package/dist/iso-block-locator.d.ts +39 -13
- package/dist/iso-block-locator.d.ts.map +1 -1
- package/dist/iso-block-locator.js +80 -25
- package/dist/iso-block-locator.js.map +2 -2
- package/dist/media-router.js.map +1 -1
- package/dist/mmtp-container-parser.d.ts +21 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +114 -11
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +113 -12
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/mmtp-decode-pipeline.js +114 -11
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-router.d.ts +18 -9
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +254 -44
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-manager.d.ts +13 -3
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +455 -103
- package/dist/multicast-manager.js.map +2 -2
- package/dist/no-modules.js +114 -11
- package/dist/no-modules.js.map +2 -2
- package/dist/repair-fetch.d.ts +36 -8
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +285 -26
- package/dist/repair-fetch.js.map +3 -3
- package/dist/track-session.d.ts.map +1 -1
- package/dist/track-session.js +1 -0
- package/dist/track-session.js.map +2 -2
- package/dist/wasm-mmtp-container-parser.js +114 -11
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +5 -5
- package/src/fec-manager.ts +156 -26
- package/src/index.ts +2 -0
- package/src/iso-block-locator.ts +114 -31
- package/src/mmtp-container-parser.ts +181 -10
- package/src/mmtp-router.ts +231 -58
- package/src/multicast-manager.ts +44 -6
- package/src/repair-fetch.ts +363 -49
- package/src/track-session.ts +3 -0
package/dist/fec-manager.js
CHANGED
|
@@ -327,6 +327,15 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
327
327
|
this.#heldEvicted = 0;
|
|
328
328
|
/** Pending holes (see `deferIncompleteGaps`): unstated, sources still held. */
|
|
329
329
|
this.#holes = [];
|
|
330
|
+
/** Holes the horizon passed while pending: sources held until closed. */
|
|
331
|
+
this.#expired = [];
|
|
332
|
+
/** Of `#expired`, those `takeExpiredHoles` has not handed out yet. */
|
|
333
|
+
this.#newlyExpired = [];
|
|
334
|
+
/**
|
|
335
|
+
* Spans closed from expired holes: behind the horizon, so kept only for
|
|
336
|
+
* `locate` while their sources are fed, until the next retirement.
|
|
337
|
+
*/
|
|
338
|
+
this.#closedBehind = [];
|
|
330
339
|
/**
|
|
331
340
|
* Keep a gap that is missing a source as a pending hole rather than a span,
|
|
332
341
|
* so a repair FETCHed for it (draft-ramadan-moq-fec §7.5) can still state
|
|
@@ -350,6 +359,9 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
350
359
|
#newest;
|
|
351
360
|
#newestSource;
|
|
352
361
|
#holes;
|
|
362
|
+
#expired;
|
|
363
|
+
#newlyExpired;
|
|
364
|
+
#closedBehind;
|
|
353
365
|
static #checkedRetention(retentionBlocks) {
|
|
354
366
|
if (!Number.isSafeInteger(retentionBlocks) || retentionBlocks <= 0) {
|
|
355
367
|
throw new Error(
|
|
@@ -419,17 +431,29 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
419
431
|
}
|
|
420
432
|
/** The known block or span containing `ssId`, with its ESI. */
|
|
421
433
|
locate(ssId) {
|
|
422
|
-
for (
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
434
|
+
for (const blocks of [this.#blocks, this.#closedBehind]) {
|
|
435
|
+
for (let i = blocks.length - 1; i >= 0; i--) {
|
|
436
|
+
const block = blocks[i];
|
|
437
|
+
if (isoBlockContains(block.start, block.length, ssId)) {
|
|
438
|
+
return { block, esi: serial32Distance(block.start, ssId) };
|
|
439
|
+
}
|
|
426
440
|
}
|
|
427
441
|
}
|
|
428
442
|
return null;
|
|
429
443
|
}
|
|
430
|
-
/** Number of pending
|
|
444
|
+
/** Number of holes not closed yet, pending or past the horizon. */
|
|
431
445
|
get holeCount() {
|
|
432
|
-
return this.#holes.length;
|
|
446
|
+
return this.#holes.length + this.#expired.length;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* The pending holes the horizon has passed since the last call, in the
|
|
450
|
+
* order it passed them. Each is no longer pending: no repair states a block
|
|
451
|
+
* in it (that repair is `stale`, as for a span the horizon retired) and the
|
|
452
|
+
* release gate does not wait for it. Its owner must close it
|
|
453
|
+
* (`closeHolesWithin`), which turns it into a span that is not kept here.
|
|
454
|
+
*/
|
|
455
|
+
takeExpiredHoles() {
|
|
456
|
+
return this.#newlyExpired.splice(0);
|
|
433
457
|
}
|
|
434
458
|
/** The stated block (never a span) ending exactly at `end`. */
|
|
435
459
|
blockEndingAt(end) {
|
|
@@ -485,9 +509,11 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
485
509
|
/**
|
|
486
510
|
* Apply one repair symbol's `(SS_Start, SSB_length)`. The first repair of a
|
|
487
511
|
* block states its range and places every held source in it; later repair
|
|
488
|
-
* symbols of the same block change nothing. A range
|
|
489
|
-
*
|
|
490
|
-
*
|
|
512
|
+
* symbols of the same block change nothing. A repair whose range is exactly
|
|
513
|
+
* a repair-less span states that span's one block. A range that contradicts
|
|
514
|
+
* a known block (a different length, or an overlap with another block or
|
|
515
|
+
* with part of a span) fails closed: the repair symbol is dropped and no
|
|
516
|
+
* block changes.
|
|
491
517
|
* A newly stated block retires behind its SS_Start (see the class note).
|
|
492
518
|
* A block so far past `newest` that stating it would retire the position
|
|
493
519
|
* `newest` is refused as `ahead`. When `newest` is a placed block's SS_Start
|
|
@@ -506,7 +532,16 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
506
532
|
const known = this.blockAt(start);
|
|
507
533
|
if (known !== void 0) {
|
|
508
534
|
if (known.span) {
|
|
509
|
-
|
|
535
|
+
if (known.length === length) {
|
|
536
|
+
const block2 = { start, length, span: false };
|
|
537
|
+
this.#blocks[this.#blocks.indexOf(known)] = block2;
|
|
538
|
+
return { status: "placed", block: block2, placed: [] };
|
|
539
|
+
}
|
|
540
|
+
return length < known.length ? {
|
|
541
|
+
status: "overlap",
|
|
542
|
+
reason: `repair block ${start}+${length} is only the first block of repair-less span ${known.start}+${known.length}`,
|
|
543
|
+
ofSpan: known
|
|
544
|
+
} : { status: "overlap", reason: `repair block ${start}+${length} runs past repair-less span ${known.start}+${known.length}` };
|
|
510
545
|
}
|
|
511
546
|
if (known.length !== length) {
|
|
512
547
|
return {
|
|
@@ -574,18 +609,28 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
574
609
|
return span === void 0 ? { status: "placed", block, placed } : { status: "placed", block, placed, span };
|
|
575
610
|
}
|
|
576
611
|
/**
|
|
577
|
-
* Turn every
|
|
578
|
-
*
|
|
579
|
-
*
|
|
612
|
+
* Turn every hole inside `[start, start + length)` into a repair-less span,
|
|
613
|
+
* claiming its held sources: its range FETCH ended, so nothing more will
|
|
614
|
+
* state its blocks. A hole the horizon passed (`takeExpiredHoles`) leaves a
|
|
615
|
+
* span that is not kept among the blocks, as the horizon would retire it at
|
|
616
|
+
* once; only `locate` finds it, until the next retirement, so its sources
|
|
617
|
+
* can be fed. Returns the spans, in SS_ID order.
|
|
580
618
|
*/
|
|
581
619
|
closeHolesWithin(start, length) {
|
|
582
620
|
const closed = [];
|
|
583
|
-
for (const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
621
|
+
for (const holes of [this.#holes, this.#expired]) {
|
|
622
|
+
for (const hole of [...holes]) {
|
|
623
|
+
if (!isoBlockContains(start, length, hole.start)) continue;
|
|
624
|
+
holes.splice(holes.indexOf(hole), 1);
|
|
625
|
+
const spanBlock = { start: hole.start, length: hole.length, span: true };
|
|
626
|
+
closed.push({ block: spanBlock, placed: this.#claim(spanBlock) });
|
|
627
|
+
if (holes === this.#holes) {
|
|
628
|
+
this.#blocks.push(spanBlock);
|
|
629
|
+
} else {
|
|
630
|
+
this.#closedBehind.push(spanBlock);
|
|
631
|
+
if (this.#newlyExpired.includes(hole)) this.#newlyExpired.splice(this.#newlyExpired.indexOf(hole), 1);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
589
634
|
}
|
|
590
635
|
return closed.sort((x, y) => serial32Distance(start, x.block.start) - serial32Distance(start, y.block.start));
|
|
591
636
|
}
|
|
@@ -599,15 +644,17 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
599
644
|
return void 0;
|
|
600
645
|
}
|
|
601
646
|
/**
|
|
602
|
-
* Retire every block and span whose last SS_ID, and every held source
|
|
603
|
-
* lies more than `retentionBlocks` blocks behind
|
|
604
|
-
* lower-bound metric (`isoBlocksBehindLowerBound`)
|
|
605
|
-
* horizon uses
|
|
647
|
+
* Retire every block and span whose last SS_ID, and every held source
|
|
648
|
+
* outside a hole, lies more than `retentionBlocks` blocks behind
|
|
649
|
+
* `position`, with the shared lower-bound metric (`isoBlocksBehindLowerBound`)
|
|
650
|
+
* every ISO interleave horizon uses; a pending hole so far behind expires
|
|
651
|
+
* (`takeExpiredHoles`). Returns the retired block starts.
|
|
606
652
|
*/
|
|
607
653
|
retireBehind(position) {
|
|
608
654
|
this.observe(position);
|
|
609
655
|
const horizon = this.#retentionBlocks;
|
|
610
656
|
const retired = [];
|
|
657
|
+
this.#closedBehind.length = 0;
|
|
611
658
|
for (let i = this.#blocks.length - 1; i >= 0; i--) {
|
|
612
659
|
const block = this.#blocks[i];
|
|
613
660
|
const last = serial32Add(block.start, block.length - 1);
|
|
@@ -619,10 +666,15 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
619
666
|
for (let i = this.#holes.length - 1; i >= 0; i--) {
|
|
620
667
|
const hole = this.#holes[i];
|
|
621
668
|
const last = serial32Add(hole.start, hole.length - 1);
|
|
622
|
-
if (isoBlocksBehindLowerBound(last, position, this.#kMax)
|
|
669
|
+
if (isoBlocksBehindLowerBound(last, position, this.#kMax) <= horizon) continue;
|
|
670
|
+
this.#holes.splice(i, 1);
|
|
671
|
+
this.#expired.push(hole);
|
|
672
|
+
this.#newlyExpired.push(hole);
|
|
623
673
|
}
|
|
624
674
|
for (const ssId of [...this.#held.keys()]) {
|
|
625
|
-
if (isoBlocksBehindLowerBound(ssId, position, this.#kMax) > horizon) this.#
|
|
675
|
+
if (isoBlocksBehindLowerBound(ssId, position, this.#kMax) > horizon && !this.#holes.some((hole) => isoBlockContains(hole.start, hole.length, ssId)) && !this.#expired.some((hole) => isoBlockContains(hole.start, hole.length, ssId))) {
|
|
676
|
+
this.#held.delete(ssId);
|
|
677
|
+
}
|
|
626
678
|
}
|
|
627
679
|
return retired;
|
|
628
680
|
}
|
|
@@ -630,6 +682,9 @@ var IsoBlockLocator = class _IsoBlockLocator {
|
|
|
630
682
|
reset() {
|
|
631
683
|
this.#blocks.length = 0;
|
|
632
684
|
this.#holes.length = 0;
|
|
685
|
+
this.#expired.length = 0;
|
|
686
|
+
this.#newlyExpired.length = 0;
|
|
687
|
+
this.#closedBehind.length = 0;
|
|
633
688
|
this.#held.clear();
|
|
634
689
|
this.#coverageEnd = void 0;
|
|
635
690
|
this.#newest = void 0;
|
|
@@ -1192,6 +1247,10 @@ var FECManager = class _FECManager {
|
|
|
1192
1247
|
// 'pending' forever, which would park every later frame of its stream in
|
|
1193
1248
|
// the in-order release gate. Each entry owns its handle; the callback
|
|
1194
1249
|
// checks it still owns the key, so a settled or reset key is never touched.
|
|
1250
|
+
/**
|
|
1251
|
+
* Per owed Worker cleanup: the bound on its acknowledgement, when its block
|
|
1252
|
+
* was due, and when its cleanup was asked for (see `onCleanupComplete`).
|
|
1253
|
+
*/
|
|
1195
1254
|
this.#pendingWorkerCleanupBounds = /* @__PURE__ */ new Map();
|
|
1196
1255
|
// Source symbol count per track-scoped block — marks complete when K symbols arrive.
|
|
1197
1256
|
this.#blockSourceCounts = /* @__PURE__ */ new Map();
|
|
@@ -1208,6 +1267,9 @@ var FECManager = class _FECManager {
|
|
|
1208
1267
|
// SSB_length source arrivals and recoverable from SSB_length received
|
|
1209
1268
|
// symbols; waiting for K fails every one of them.
|
|
1210
1269
|
this.#blockAuthoredLengths = /* @__PURE__ */ new Map();
|
|
1270
|
+
// Keys whose authored length is a repair-less span's (#openIsoSpan), until
|
|
1271
|
+
// a repair states the block there or the span resolves.
|
|
1272
|
+
this.#isoSpanKeys = /* @__PURE__ */ new Set();
|
|
1211
1273
|
this.#ssbLengthConflictCount = 0;
|
|
1212
1274
|
this.#blockRepairRsIds = /* @__PURE__ */ new Map();
|
|
1213
1275
|
// RSB_length per block (C.5.3 Repair FEC Payload ID): how many repair
|
|
@@ -1405,7 +1467,10 @@ var FECManager = class _FECManager {
|
|
|
1405
1467
|
// arrives via onBlockRecovered callback.
|
|
1406
1468
|
this.onBlockStarted = null;
|
|
1407
1469
|
this.onBlockComplete = null;
|
|
1408
|
-
/**
|
|
1470
|
+
/**
|
|
1471
|
+
* A block reached its fail bound. `blockClass` is its class at failure.
|
|
1472
|
+
* `span` is set when what failed is a repair-less span starting at `sbn`.
|
|
1473
|
+
*/
|
|
1409
1474
|
this.onBlockFailed = null;
|
|
1410
1475
|
/**
|
|
1411
1476
|
* Fired once per stream when its Worker decoder stops being routable.
|
|
@@ -1447,6 +1512,8 @@ var FECManager = class _FECManager {
|
|
|
1447
1512
|
* for it, which it must be.
|
|
1448
1513
|
*/
|
|
1449
1514
|
this.#onUndelimitedLoss = null;
|
|
1515
|
+
/** Set with the loss handler: true takes an expired hole to close (`closeIsoHoles`). */
|
|
1516
|
+
this.#onIsoHoleExpired = null;
|
|
1450
1517
|
/** A block every symbol of which vanished was created by SBN evidence. */
|
|
1451
1518
|
this.onBlockVanished = null;
|
|
1452
1519
|
/** A block reached a terminal state (completed, recovered, or failed). */
|
|
@@ -1491,6 +1558,7 @@ var FECManager = class _FECManager {
|
|
|
1491
1558
|
#blockSourceEsis;
|
|
1492
1559
|
#blockRebuiltEsis;
|
|
1493
1560
|
#blockAuthoredLengths;
|
|
1561
|
+
#isoSpanKeys;
|
|
1494
1562
|
#ssbLengthConflictCount;
|
|
1495
1563
|
#blockRepairRsIds;
|
|
1496
1564
|
#blockRsbLengths;
|
|
@@ -1557,6 +1625,7 @@ var FECManager = class _FECManager {
|
|
|
1557
1625
|
#decoderUnavailableEvents;
|
|
1558
1626
|
#repairSymbolSizeMismatchCount;
|
|
1559
1627
|
#onUndelimitedLoss;
|
|
1628
|
+
#onIsoHoleExpired;
|
|
1560
1629
|
#encodeTrackName(catalogTrackName) {
|
|
1561
1630
|
let encoded = this.#encodedTrackNames.get(catalogTrackName);
|
|
1562
1631
|
if (encoded === void 0) {
|
|
@@ -2028,7 +2097,10 @@ var FECManager = class _FECManager {
|
|
|
2028
2097
|
timer.handle = null;
|
|
2029
2098
|
this.#requestRepair(sbn, packetId, catalogTrackName);
|
|
2030
2099
|
};
|
|
2031
|
-
timer.handle = setTimeout(
|
|
2100
|
+
timer.handle = setTimeout(
|
|
2101
|
+
onFire,
|
|
2102
|
+
this.#receiptAtMs === void 0 ? Math.max(0, deadlineMs - performance.now()) : this.#receiptTimerDelayMs(deadlineMs, 0)
|
|
2103
|
+
);
|
|
2032
2104
|
this.#repairDeadlines.set(key, timer);
|
|
2033
2105
|
}
|
|
2034
2106
|
#requestRepair(sbn, packetId, catalogTrackName, settledByOrder = false) {
|
|
@@ -2172,8 +2244,14 @@ var FECManager = class _FECManager {
|
|
|
2172
2244
|
* Vanished blocks are not enumerated here: their starts are unknown until a
|
|
2173
2245
|
* repair states them. The SS_IDs between two stated blocks become one
|
|
2174
2246
|
* repair-less span when the later block's repair arrives (placeIsoRepair).
|
|
2247
|
+
*
|
|
2248
|
+
* A `held` source arrived before its block was known and is fed only now
|
|
2249
|
+
* that a repair, a span or a closed hole placed it: how far its block start
|
|
2250
|
+
* then lies behind the frontier measures how long the receiver held it, not
|
|
2251
|
+
* where the stream was, so only its SS_ID and packet_sequence_number judge
|
|
2252
|
+
* it (BLO-52085).
|
|
2175
2253
|
*/
|
|
2176
|
-
#noteSbnSeen(sbn, packetId, catalogTrackName, symbol, packetSequenceNumber, ssId = sbn) {
|
|
2254
|
+
#noteSbnSeen(sbn, packetId, catalogTrackName, symbol, packetSequenceNumber, ssId = sbn, held = false) {
|
|
2177
2255
|
const streamKey = this.#streamKey(packetId, catalogTrackName);
|
|
2178
2256
|
const now = performance.now();
|
|
2179
2257
|
const previous = (symbol === "source" ? this.#sourceFrontiers : this.#highestSbn).get(streamKey);
|
|
@@ -2212,7 +2290,7 @@ var FECManager = class _FECManager {
|
|
|
2212
2290
|
return sent <= 2147483647 && serial32Distance(from.ssId, to.ssId) <= sent + config.k;
|
|
2213
2291
|
};
|
|
2214
2292
|
if (!this.#blockPrecedes(previous.sbn, sbn)) {
|
|
2215
|
-
if (symbol === "source" && window !== null && this.#blockPrecedes(sbn, previous.sbn) && (this.#blocksBehind(sbn, previous.sbn, packetId) > this.#isoRetentionBlocks(packetId) || psn !== void 0 && anchor !== void 0 && serial32Precedes(ssId, anchor.ssId) && psn !== anchor.psn && !sentInOrder({ ssId, psn }, anchor))) {
|
|
2293
|
+
if (symbol === "source" && window !== null && this.#blockPrecedes(sbn, previous.sbn) && (!held && this.#blocksBehind(sbn, previous.sbn, packetId) > this.#isoRetentionBlocks(packetId) || psn !== void 0 && anchor !== void 0 && serial32Precedes(ssId, anchor.ssId) && psn !== anchor.psn && !sentInOrder({ ssId, psn }, anchor))) {
|
|
2216
2294
|
discontinuity();
|
|
2217
2295
|
return;
|
|
2218
2296
|
}
|
|
@@ -2474,17 +2552,21 @@ var FECManager = class _FECManager {
|
|
|
2474
2552
|
this.#blockWaitDeadlinesMs.delete(key);
|
|
2475
2553
|
this.#repairHoldUntilMs.delete(key);
|
|
2476
2554
|
this.#traceLifecycle("timeout-queued", sbn, packetId, catalogTrackName);
|
|
2477
|
-
this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName);
|
|
2555
|
+
this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName, currentDeadlineMs ?? firedAtMs);
|
|
2478
2556
|
};
|
|
2479
|
-
timer.handle = setTimeout(
|
|
2557
|
+
timer.handle = setTimeout(
|
|
2558
|
+
onFire,
|
|
2559
|
+
this.#receiptAtMs === void 0 ? Math.max(1, Math.ceil(deadlineMs - nowMs)) : this.#receiptTimerDelayMs(deadlineMs, 1)
|
|
2560
|
+
);
|
|
2480
2561
|
this.#activeBlocks.set(key, timer);
|
|
2481
2562
|
this.#indexBlockKey(key, sbn, packetId);
|
|
2482
2563
|
}
|
|
2483
2564
|
/**
|
|
2484
2565
|
* Hand a block whose delivery is over to its decoder for finalization: the
|
|
2485
|
-
* datagram deadline fired, or the ordered relay moved past it.
|
|
2566
|
+
* datagram deadline fired, or the ordered relay moved past it. `dueAtMs`
|
|
2567
|
+
* is when it was due: its deadline, or now.
|
|
2486
2568
|
*/
|
|
2487
|
-
#finalizeExpiredBlock(sbn, packetId, catalogTrackName) {
|
|
2569
|
+
#finalizeExpiredBlock(sbn, packetId, catalogTrackName, dueAtMs = performance.now()) {
|
|
2488
2570
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
2489
2571
|
if (this.#materializedBlocks.has(key) && !this.#blockPathCounts.has(key)) {
|
|
2490
2572
|
this.#failBlock(sbn, packetId, catalogTrackName);
|
|
@@ -2500,7 +2582,7 @@ var FECManager = class _FECManager {
|
|
|
2500
2582
|
if (workerClient) {
|
|
2501
2583
|
this.#pendingWorkerCleanups.add(key);
|
|
2502
2584
|
this.#pendingWorkerCleanupClients.set(key, workerClient);
|
|
2503
|
-
this.#boundOwedCleanup(key, packetId, workerClient);
|
|
2585
|
+
this.#boundOwedCleanup(key, packetId, workerClient, dueAtMs);
|
|
2504
2586
|
workerClient.cleanup(sbn);
|
|
2505
2587
|
} else {
|
|
2506
2588
|
const decoder = this.#decoderForPacket(packetId, catalogTrackName)?.client;
|
|
@@ -2814,6 +2896,7 @@ var FECManager = class _FECManager {
|
|
|
2814
2896
|
resetMap(this.#blockSourceEsis);
|
|
2815
2897
|
resetMap(this.#blockRebuiltEsis);
|
|
2816
2898
|
resetMap(this.#blockAuthoredLengths);
|
|
2899
|
+
resetSet(this.#isoSpanKeys);
|
|
2817
2900
|
resetMap(this.#blockRepairRsIds);
|
|
2818
2901
|
resetMap(this.#blockRsbLengths);
|
|
2819
2902
|
resetMap(this.#heldLayerStates);
|
|
@@ -2939,6 +3022,7 @@ var FECManager = class _FECManager {
|
|
|
2939
3022
|
dropMap(this.#blockSourceEsis);
|
|
2940
3023
|
dropMap(this.#blockRebuiltEsis);
|
|
2941
3024
|
dropMap(this.#blockAuthoredLengths);
|
|
3025
|
+
dropSet(this.#isoSpanKeys);
|
|
2942
3026
|
dropMap(this.#blockRepairRsIds);
|
|
2943
3027
|
dropMap(this.#blockRsbLengths);
|
|
2944
3028
|
dropMap(this.#blockPathCounts);
|
|
@@ -3281,8 +3365,9 @@ var FECManager = class _FECManager {
|
|
|
3281
3365
|
return true;
|
|
3282
3366
|
}
|
|
3283
3367
|
/**
|
|
3284
|
-
* Drop a block's repair hold: its FETCH ended without delivering
|
|
3285
|
-
*
|
|
3368
|
+
* Drop a block's repair hold: its FETCH ended without delivering, or brought
|
|
3369
|
+
* back only a repair that cannot complete it. The block falls back to its
|
|
3370
|
+
* multicast wait, which may already have passed.
|
|
3286
3371
|
*/
|
|
3287
3372
|
releaseRepairHold(sbn, packetId, catalogTrackName) {
|
|
3288
3373
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
@@ -3420,6 +3505,7 @@ var FECManager = class _FECManager {
|
|
|
3420
3505
|
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName);
|
|
3421
3506
|
const evictedBefore = locator.heldEvicted;
|
|
3422
3507
|
const held = locator.hold(ssId, source);
|
|
3508
|
+
this.#settleExpiredIsoHoles(locator, packetId, catalogTrackName);
|
|
3423
3509
|
if (held && source.source === "multicast") this.#noteMulticastSource(locator, ssId, packetId, catalogTrackName);
|
|
3424
3510
|
const evicted = locator.heldEvicted - evictedBefore;
|
|
3425
3511
|
if (evicted > 0) {
|
|
@@ -3441,17 +3527,20 @@ var FECManager = class _FECManager {
|
|
|
3441
3527
|
* contradicts a known block is refused (counted) and must be dropped.
|
|
3442
3528
|
*/
|
|
3443
3529
|
placeIsoRepair(packetId, catalogTrackName, ssStart, ssbLength) {
|
|
3444
|
-
const
|
|
3530
|
+
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName);
|
|
3531
|
+
const placement = locator.placeRepair(ssStart, ssbLength);
|
|
3532
|
+
this.#settleExpiredIsoHoles(locator, packetId, catalogTrackName);
|
|
3445
3533
|
if (placement.status !== "placed") {
|
|
3446
3534
|
this.#isoRepairRefusedCount++;
|
|
3447
3535
|
if (this.#isoRepairRefusedCount <= 5 || this.#isoRepairRefusedCount % 100 === 0) {
|
|
3448
3536
|
console.warn(`[FECManager] packetId=${packetId} iso-ssbg1-v1 repair refused (${placement.status}): ${placement.reason}`);
|
|
3449
3537
|
}
|
|
3538
|
+
if (placement.ofSpan !== void 0) this.releaseRepairHold(placement.ofSpan.start, packetId, catalogTrackName);
|
|
3450
3539
|
return placement;
|
|
3451
3540
|
}
|
|
3541
|
+
this.#isoSpanKeys.delete(this.#blockKey(placement.block.start, packetId, catalogTrackName));
|
|
3452
3542
|
if (placement.span !== void 0) this.#openIsoSpan(placement.span, packetId, catalogTrackName);
|
|
3453
3543
|
if (placement.hole !== void 0) {
|
|
3454
|
-
const locator = this.#isoLocatorFor(packetId, catalogTrackName);
|
|
3455
3544
|
const hole = placement.hole.block;
|
|
3456
3545
|
const previous = locator.blockEndingAt(hole.start);
|
|
3457
3546
|
const loss = {
|
|
@@ -3472,17 +3561,29 @@ var FECManager = class _FECManager {
|
|
|
3472
3561
|
* `handler`, instead of a repair-less span; null restores spans. Set by the
|
|
3473
3562
|
* range repair scheduler; applies to every stream from its next gap. The
|
|
3474
3563
|
* handler and the deferral are one setting, so no hole is ever created with
|
|
3475
|
-
* nobody to close it.
|
|
3564
|
+
* nobody to close it. `onExpired` is told of each pending hole the stream
|
|
3565
|
+
* runs past the locator's horizon (BLO-52085): returning true, it closes
|
|
3566
|
+
* the hole (`closeIsoHoles`), so the span it leaves still fails and is
|
|
3567
|
+
* reported; otherwise the hole is discarded with its sources.
|
|
3476
3568
|
*/
|
|
3477
|
-
setUndelimitedLossHandler(handler) {
|
|
3569
|
+
setUndelimitedLossHandler(handler, onExpired = null) {
|
|
3478
3570
|
this.#onUndelimitedLoss = handler;
|
|
3571
|
+
this.#onIsoHoleExpired = handler === null ? null : onExpired;
|
|
3479
3572
|
this.#deferIsoGaps = handler !== null;
|
|
3480
3573
|
for (const locator of this.#isoLocators.values()) locator.deferIncompleteGaps = this.#deferIsoGaps;
|
|
3481
3574
|
}
|
|
3575
|
+
/** Hand each hole the locator's horizon passed to its owner, or discard it (setUndelimitedLossHandler). */
|
|
3576
|
+
#settleExpiredIsoHoles(locator, packetId, catalogTrackName) {
|
|
3577
|
+
for (const hole of locator.takeExpiredHoles()) {
|
|
3578
|
+
const expired = { packetId, catalogTrackName, holeStart: hole.start, holeLength: hole.length };
|
|
3579
|
+
if (this.#onIsoHoleExpired?.(expired) !== true) locator.closeHolesWithin(hole.start, hole.length);
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3482
3582
|
/**
|
|
3483
|
-
* The range repair for a hole ended: every hole
|
|
3484
|
-
*
|
|
3485
|
-
* with the held sources they placed, for the caller to feed
|
|
3583
|
+
* The range repair for a hole ended: every hole inside `[start, start +
|
|
3584
|
+
* length)`, pending or expired, becomes a repair-less span. Returns those
|
|
3585
|
+
* spans with the held sources they placed, for the caller to feed
|
|
3586
|
+
* (MmtpRouter).
|
|
3486
3587
|
*/
|
|
3487
3588
|
closeIsoHoles(packetId, catalogTrackName, start, length) {
|
|
3488
3589
|
const locator = this.#isoLocators.get(this.#streamKey(packetId, catalogTrackName));
|
|
@@ -3501,7 +3602,10 @@ var FECManager = class _FECManager {
|
|
|
3501
3602
|
*/
|
|
3502
3603
|
#openIsoSpan(span, packetId, catalogTrackName) {
|
|
3503
3604
|
const key = this.#blockKey(span.block.start, packetId, catalogTrackName);
|
|
3504
|
-
if (!this.#blockAuthoredLengths.has(key))
|
|
3605
|
+
if (!this.#blockAuthoredLengths.has(key)) {
|
|
3606
|
+
this.#blockAuthoredLengths.set(key, span.block.length);
|
|
3607
|
+
this.#isoSpanKeys.add(key);
|
|
3608
|
+
}
|
|
3505
3609
|
if (span.placed.length === 0) this.#materializeBlock(span.block.start, packetId, catalogTrackName, true);
|
|
3506
3610
|
}
|
|
3507
3611
|
/**
|
|
@@ -3842,11 +3946,14 @@ var FECManager = class _FECManager {
|
|
|
3842
3946
|
});
|
|
3843
3947
|
client.onCleanupComplete((sbn) => {
|
|
3844
3948
|
if (this.#releasedWorkerClients.has(client)) return;
|
|
3845
|
-
const
|
|
3949
|
+
const owed = [...this.#pendingWorkerCleanups].filter((key) => this.#pendingWorkerCleanupClients.get(key) === client);
|
|
3950
|
+
const cleanupKeys = owed.filter((key) => {
|
|
3846
3951
|
const block = this.#parseBlockKey(key);
|
|
3847
|
-
return block.packetId === packetId && block.sbn === sbn
|
|
3952
|
+
return block.packetId === packetId && block.sbn === sbn;
|
|
3848
3953
|
});
|
|
3849
|
-
|
|
3954
|
+
const askedAtMs = Math.max(...cleanupKeys.map((key) => this.#pendingWorkerCleanupBounds.get(key)?.askedAtMs ?? -Infinity));
|
|
3955
|
+
const due = owed.filter((key) => !cleanupKeys.includes(key) && (this.#pendingWorkerCleanupBounds.get(key)?.dueAtMs ?? Infinity) <= askedAtMs);
|
|
3956
|
+
for (const key of [...cleanupKeys, ...due]) this.#settleOwedCleanup(key, "cleanup-ack");
|
|
3850
3957
|
});
|
|
3851
3958
|
client.onError((msg, code) => {
|
|
3852
3959
|
const requestedKind = code ?? msg;
|
|
@@ -3916,9 +4023,11 @@ var FECManager = class _FECManager {
|
|
|
3916
4023
|
* fail deadline already passed, so failing it is the decision that deadline
|
|
3917
4024
|
* made. A late ack then finds nothing owed and is a no-op.
|
|
3918
4025
|
*/
|
|
3919
|
-
#boundOwedCleanup(key, packetId, client) {
|
|
4026
|
+
#boundOwedCleanup(key, packetId, client, dueAtMs) {
|
|
3920
4027
|
this.#clearOwedCleanupBound(key);
|
|
3921
4028
|
const bound = {
|
|
4029
|
+
dueAtMs,
|
|
4030
|
+
askedAtMs: performance.now(),
|
|
3922
4031
|
handle: setTimeout(() => {
|
|
3923
4032
|
if (this.#pendingWorkerCleanupBounds.get(key) !== bound) return;
|
|
3924
4033
|
this.#pendingWorkerCleanupBounds.delete(key);
|
|
@@ -4489,12 +4598,13 @@ var FECManager = class _FECManager {
|
|
|
4489
4598
|
* the inactivity deadline on each symbol, records the symbol's in-band
|
|
4490
4599
|
* classification evidence, and marks the block clean when all of its
|
|
4491
4600
|
* authored source symbols arrived: the SSB_length a repair symbol declared,
|
|
4492
|
-
* otherwise K.
|
|
4601
|
+
* otherwise K. `held` marks a source held unplaced since it arrived and
|
|
4602
|
+
* placed only now (see #noteSbnSeen).
|
|
4493
4603
|
*
|
|
4494
4604
|
* Throws for a packetId that was never configured, so a classified block
|
|
4495
4605
|
* never reads the manager-global K or timeout.
|
|
4496
4606
|
*/
|
|
4497
|
-
trackBlock(sbn, packetId, catalogTrackName, esi, evidence) {
|
|
4607
|
+
trackBlock(sbn, packetId, catalogTrackName, esi, evidence, held = false) {
|
|
4498
4608
|
const trackConfig = this.#trackConfigs.get(packetId);
|
|
4499
4609
|
if (trackConfig === void 0) {
|
|
4500
4610
|
throw new Error(
|
|
@@ -4510,7 +4620,8 @@ var FECManager = class _FECManager {
|
|
|
4510
4620
|
catalogTrackName,
|
|
4511
4621
|
"source",
|
|
4512
4622
|
evidence.packetSequenceNumber,
|
|
4513
|
-
Number.isInteger(esi) ? sbn + esi >>> 0 : sbn
|
|
4623
|
+
Number.isInteger(esi) ? sbn + esi >>> 0 : sbn,
|
|
4624
|
+
held
|
|
4514
4625
|
);
|
|
4515
4626
|
const authoredLength = this.#blockAuthoredLengths.get(key) ?? k;
|
|
4516
4627
|
if (Number.isInteger(esi) && esi >= authoredLength) {
|
|
@@ -4565,6 +4676,7 @@ var FECManager = class _FECManager {
|
|
|
4565
4676
|
this.#blockSourceEsis.delete(key);
|
|
4566
4677
|
this.#blockRebuiltEsis.delete(key);
|
|
4567
4678
|
this.#blockAuthoredLengths.delete(key);
|
|
4679
|
+
this.#isoSpanKeys.delete(key);
|
|
4568
4680
|
this.#blockRepairRsIds.delete(key);
|
|
4569
4681
|
this.#blockRsbLengths.delete(key);
|
|
4570
4682
|
this.#markTerminal(key, sbn, packetId, true);
|
|
@@ -4595,6 +4707,19 @@ var FECManager = class _FECManager {
|
|
|
4595
4707
|
#receiptNow() {
|
|
4596
4708
|
return this.#receiptAtMs ?? performance.now();
|
|
4597
4709
|
}
|
|
4710
|
+
/**
|
|
4711
|
+
* The whole-ms delay of a timer for `deadlineMs` armed inside a receipt
|
|
4712
|
+
* scope: counted from the receipt time, as the timer of a symbol fed when
|
|
4713
|
+
* it was received was, less the whole ms since. So it fires on the tick
|
|
4714
|
+
* that timer would have, never later than a delay counted from now, and
|
|
4715
|
+
* its handler still re-arms a timer that fires before its deadline.
|
|
4716
|
+
* Counted from now, a deadline set from the receipt time is rounded up
|
|
4717
|
+
* from another fraction of a ms (BLO-52085).
|
|
4718
|
+
*/
|
|
4719
|
+
#receiptTimerDelayMs(deadlineMs, minMs) {
|
|
4720
|
+
const receivedAtMs = this.#receiptAtMs;
|
|
4721
|
+
return Math.max(minMs, Math.ceil(deadlineMs - receivedAtMs) - Math.ceil(performance.now() - receivedAtMs));
|
|
4722
|
+
}
|
|
4598
4723
|
/**
|
|
4599
4724
|
* A held packet admitted late arms from its (earlier) receipt time; that
|
|
4600
4725
|
* never pulls in a deadline later activity already armed. Outside a
|
|
@@ -4643,6 +4768,7 @@ var FECManager = class _FECManager {
|
|
|
4643
4768
|
}
|
|
4644
4769
|
#failBlock(sbn, packetId, catalogTrackName) {
|
|
4645
4770
|
const key = this.#blockKey(sbn, packetId, catalogTrackName);
|
|
4771
|
+
const span = this.#isoSpanKeys.delete(key) ? { length: this.#blockAuthoredLengths.get(key) ?? 0, sources: this.#blockSourceCounts.get(key) ?? 0 } : void 0;
|
|
4646
4772
|
this.#traceLifecycle("failed", sbn, packetId, catalogTrackName);
|
|
4647
4773
|
this.#clearRepairDeadline(key);
|
|
4648
4774
|
this.#clearBlockTimer(key);
|
|
@@ -4662,7 +4788,8 @@ var FECManager = class _FECManager {
|
|
|
4662
4788
|
this.#markPathBlockComplete(sbn, packetId, catalogTrackName);
|
|
4663
4789
|
this.#fecStats.blocksFailed++;
|
|
4664
4790
|
this.#statsForPacket(packetId).blocksFailed++;
|
|
4665
|
-
this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);
|
|
4791
|
+
if (span === void 0) this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);
|
|
4792
|
+
else this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass, span);
|
|
4666
4793
|
this.onBlockResolved?.(sbn, packetId, catalogTrackName);
|
|
4667
4794
|
}
|
|
4668
4795
|
/**
|
|
@@ -4693,6 +4820,7 @@ var FECManager = class _FECManager {
|
|
|
4693
4820
|
this.#blockSourceEsis.delete(key);
|
|
4694
4821
|
this.#blockRebuiltEsis.delete(key);
|
|
4695
4822
|
this.#blockAuthoredLengths.delete(key);
|
|
4823
|
+
this.#isoSpanKeys.delete(key);
|
|
4696
4824
|
this.#blockRepairRsIds.delete(key);
|
|
4697
4825
|
this.#blockRsbLengths.delete(key);
|
|
4698
4826
|
this.#clearBlockTimer(key);
|
|
@@ -4785,6 +4913,7 @@ var FECManager = class _FECManager {
|
|
|
4785
4913
|
this.#blockSourceEsis.clear();
|
|
4786
4914
|
this.#blockRebuiltEsis.clear();
|
|
4787
4915
|
this.#blockAuthoredLengths.clear();
|
|
4916
|
+
this.#isoSpanKeys.clear();
|
|
4788
4917
|
this.#blockRepairRsIds.clear();
|
|
4789
4918
|
this.#blockRsbLengths.clear();
|
|
4790
4919
|
this.#heldLayerStates.clear();
|