@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockcast/mmt-container",
|
|
3
|
-
"version": "2.2.0-main.
|
|
3
|
+
"version": "2.2.0-main.7e8a9d13c01c",
|
|
4
4
|
"description": "MMTP/LOC container parsing for MMT over MoQ and SSM",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -169,10 +169,10 @@
|
|
|
169
169
|
"test:watch": "vitest"
|
|
170
170
|
},
|
|
171
171
|
"dependencies": {
|
|
172
|
-
"@blockcast/fec-worker": "2.2.0-main.
|
|
173
|
-
"@blockcast/mmt-base": "2.2.0-main.
|
|
174
|
-
"@blockcast/mmt-fec": "2.2.0-main.
|
|
175
|
-
"@blockcast/mmt-manifest-verify": "2.2.0-main.
|
|
172
|
+
"@blockcast/fec-worker": "2.2.0-main.7e8a9d13c01c",
|
|
173
|
+
"@blockcast/mmt-base": "2.2.0-main.7e8a9d13c01c",
|
|
174
|
+
"@blockcast/mmt-fec": "2.2.0-main.7e8a9d13c01c",
|
|
175
|
+
"@blockcast/mmt-manifest-verify": "2.2.0-main.7e8a9d13c01c",
|
|
176
176
|
"@noble/curves": "^1.9.7",
|
|
177
177
|
"@noble/hashes": "^1.8.0",
|
|
178
178
|
"ajv": "^8.17.1",
|
package/src/fec-manager.ts
CHANGED
|
@@ -213,6 +213,25 @@ export interface UndelimitedIsoLoss {
|
|
|
213
213
|
readonly previousBlockStart?: number
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
/**
|
|
217
|
+
* A pending hole the stream ran more than the locator's horizon past before
|
|
218
|
+
* its range repair ended (BLO-52085): no repair can state its blocks any
|
|
219
|
+
* more, and the release gate no longer waits for it.
|
|
220
|
+
*/
|
|
221
|
+
export type ExpiredIsoHole = Pick<UndelimitedIsoLoss, 'packetId' | 'catalogTrackName' | 'holeStart' | 'holeLength'>
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* What a failed iso-ssbg1-v1 repair-less span was: the `length` SS_IDs from
|
|
225
|
+
* its start, between two stated blocks, of which `sources` arrived. No repair
|
|
226
|
+
* stated the blocks inside it, so the receiver cannot tell its complete
|
|
227
|
+
* blocks from the ones that lost a source: the span failed, not the block
|
|
228
|
+
* that starts it.
|
|
229
|
+
*/
|
|
230
|
+
export interface IsoSpanFailure {
|
|
231
|
+
readonly length: number
|
|
232
|
+
readonly sources: number
|
|
233
|
+
}
|
|
234
|
+
|
|
216
235
|
/**
|
|
217
236
|
* How the multicast leg hands its symbols to this manager.
|
|
218
237
|
*
|
|
@@ -522,7 +541,14 @@ export class FECManager {
|
|
|
522
541
|
// 'pending' forever, which would park every later frame of its stream in
|
|
523
542
|
// the in-order release gate. Each entry owns its handle; the callback
|
|
524
543
|
// checks it still owns the key, so a settled or reset key is never touched.
|
|
525
|
-
|
|
544
|
+
/**
|
|
545
|
+
* Per owed Worker cleanup: the bound on its acknowledgement, when its block
|
|
546
|
+
* was due, and when its cleanup was asked for (see `onCleanupComplete`).
|
|
547
|
+
*/
|
|
548
|
+
#pendingWorkerCleanupBounds = new Map<
|
|
549
|
+
string,
|
|
550
|
+
{ handle: ReturnType<typeof setTimeout>; dueAtMs: number; askedAtMs: number }
|
|
551
|
+
>()
|
|
526
552
|
// Source symbol count per track-scoped block — marks complete when K symbols arrive.
|
|
527
553
|
#blockSourceCounts = new Map<string, number>()
|
|
528
554
|
#blockSourceEsis = new Map<string, Set<number>>()
|
|
@@ -538,6 +564,9 @@ export class FECManager {
|
|
|
538
564
|
// SSB_length source arrivals and recoverable from SSB_length received
|
|
539
565
|
// symbols; waiting for K fails every one of them.
|
|
540
566
|
#blockAuthoredLengths = new Map<string, number>()
|
|
567
|
+
// Keys whose authored length is a repair-less span's (#openIsoSpan), until
|
|
568
|
+
// a repair states the block there or the span resolves.
|
|
569
|
+
#isoSpanKeys = new Set<string>()
|
|
541
570
|
#ssbLengthConflictCount = 0
|
|
542
571
|
#blockRepairRsIds = new Map<string, Set<number>>()
|
|
543
572
|
// RSB_length per block (C.5.3 Repair FEC Payload ID): how many repair
|
|
@@ -745,13 +774,17 @@ export class FECManager {
|
|
|
745
774
|
onBlockComplete:
|
|
746
775
|
| ((sbn: number, packetId: number, catalogTrackName?: string) => void)
|
|
747
776
|
| null = null
|
|
748
|
-
/**
|
|
777
|
+
/**
|
|
778
|
+
* A block reached its fail bound. `blockClass` is its class at failure.
|
|
779
|
+
* `span` is set when what failed is a repair-less span starting at `sbn`.
|
|
780
|
+
*/
|
|
749
781
|
onBlockFailed:
|
|
750
782
|
| ((
|
|
751
783
|
sbn: number,
|
|
752
784
|
packetId: number,
|
|
753
785
|
catalogTrackName: string | undefined,
|
|
754
786
|
blockClass: BlockClass,
|
|
787
|
+
span?: IsoSpanFailure,
|
|
755
788
|
) => void)
|
|
756
789
|
| null = null
|
|
757
790
|
/**
|
|
@@ -835,6 +868,8 @@ export class FECManager {
|
|
|
835
868
|
* for it, which it must be.
|
|
836
869
|
*/
|
|
837
870
|
#onUndelimitedLoss: ((loss: UndelimitedIsoLoss) => void) | null = null
|
|
871
|
+
/** Set with the loss handler: true takes an expired hole to close (`closeIsoHoles`). */
|
|
872
|
+
#onIsoHoleExpired: ((hole: ExpiredIsoHole) => boolean) | null = null
|
|
838
873
|
/** A block every symbol of which vanished was created by SBN evidence. */
|
|
839
874
|
onBlockVanished:
|
|
840
875
|
| ((sbn: number, packetId: number, catalogTrackName: string | undefined) => void)
|
|
@@ -1489,7 +1524,12 @@ export class FECManager {
|
|
|
1489
1524
|
timer.handle = null
|
|
1490
1525
|
this.#requestRepair(sbn, packetId, catalogTrackName)
|
|
1491
1526
|
}
|
|
1492
|
-
timer.handle = setTimeout(
|
|
1527
|
+
timer.handle = setTimeout(
|
|
1528
|
+
onFire,
|
|
1529
|
+
this.#receiptAtMs === undefined
|
|
1530
|
+
? Math.max(0, deadlineMs - performance.now())
|
|
1531
|
+
: this.#receiptTimerDelayMs(deadlineMs, 0),
|
|
1532
|
+
)
|
|
1493
1533
|
this.#repairDeadlines.set(key, timer)
|
|
1494
1534
|
}
|
|
1495
1535
|
|
|
@@ -1683,6 +1723,12 @@ export class FECManager {
|
|
|
1683
1723
|
* Vanished blocks are not enumerated here: their starts are unknown until a
|
|
1684
1724
|
* repair states them. The SS_IDs between two stated blocks become one
|
|
1685
1725
|
* repair-less span when the later block's repair arrives (placeIsoRepair).
|
|
1726
|
+
*
|
|
1727
|
+
* A `held` source arrived before its block was known and is fed only now
|
|
1728
|
+
* that a repair, a span or a closed hole placed it: how far its block start
|
|
1729
|
+
* then lies behind the frontier measures how long the receiver held it, not
|
|
1730
|
+
* where the stream was, so only its SS_ID and packet_sequence_number judge
|
|
1731
|
+
* it (BLO-52085).
|
|
1686
1732
|
*/
|
|
1687
1733
|
#noteSbnSeen(
|
|
1688
1734
|
sbn: number,
|
|
@@ -1691,6 +1737,7 @@ export class FECManager {
|
|
|
1691
1737
|
symbol: 'source' | 'repair',
|
|
1692
1738
|
packetSequenceNumber?: number,
|
|
1693
1739
|
ssId = sbn,
|
|
1740
|
+
held = false,
|
|
1694
1741
|
): void {
|
|
1695
1742
|
const streamKey = this.#streamKey(packetId, catalogTrackName)
|
|
1696
1743
|
// Arrival at FEC, not receipt (withReceiptTime): this bounds how far SS_ID
|
|
@@ -1747,7 +1794,7 @@ export class FECManager {
|
|
|
1747
1794
|
}
|
|
1748
1795
|
if (!this.#blockPrecedes(previous.sbn, sbn)) {
|
|
1749
1796
|
if (symbol === 'source' && window !== null && this.#blockPrecedes(sbn, previous.sbn) && (
|
|
1750
|
-
this.#blocksBehind(sbn, previous.sbn, packetId) > this.#isoRetentionBlocks(packetId) ||
|
|
1797
|
+
(!held && this.#blocksBehind(sbn, previous.sbn, packetId) > this.#isoRetentionBlocks(packetId)) ||
|
|
1751
1798
|
(psn !== undefined && anchor !== undefined && serial32Precedes(ssId, anchor.ssId) &&
|
|
1752
1799
|
psn !== anchor.psn && !sentInOrder({ ssId, psn }, anchor))
|
|
1753
1800
|
)) {
|
|
@@ -2101,21 +2148,28 @@ export class FECManager {
|
|
|
2101
2148
|
this.#blockWaitDeadlinesMs.delete(key)
|
|
2102
2149
|
this.#repairHoldUntilMs.delete(key)
|
|
2103
2150
|
this.#traceLifecycle('timeout-queued', sbn, packetId, catalogTrackName)
|
|
2104
|
-
this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName)
|
|
2151
|
+
this.#finalizeExpiredBlock(sbn, packetId, catalogTrackName, currentDeadlineMs ?? firedAtMs)
|
|
2105
2152
|
}
|
|
2106
|
-
timer.handle = setTimeout(
|
|
2153
|
+
timer.handle = setTimeout(
|
|
2154
|
+
onFire,
|
|
2155
|
+
this.#receiptAtMs === undefined
|
|
2156
|
+
? Math.max(1, Math.ceil(deadlineMs - nowMs))
|
|
2157
|
+
: this.#receiptTimerDelayMs(deadlineMs, 1),
|
|
2158
|
+
)
|
|
2107
2159
|
this.#activeBlocks.set(key, timer)
|
|
2108
2160
|
this.#indexBlockKey(key, sbn, packetId)
|
|
2109
2161
|
}
|
|
2110
2162
|
|
|
2111
2163
|
/**
|
|
2112
2164
|
* Hand a block whose delivery is over to its decoder for finalization: the
|
|
2113
|
-
* datagram deadline fired, or the ordered relay moved past it.
|
|
2165
|
+
* datagram deadline fired, or the ordered relay moved past it. `dueAtMs`
|
|
2166
|
+
* is when it was due: its deadline, or now.
|
|
2114
2167
|
*/
|
|
2115
2168
|
#finalizeExpiredBlock(
|
|
2116
2169
|
sbn: number,
|
|
2117
2170
|
packetId: number,
|
|
2118
2171
|
catalogTrackName: string | undefined,
|
|
2172
|
+
dueAtMs = performance.now(),
|
|
2119
2173
|
): void {
|
|
2120
2174
|
const key = this.#blockKey(sbn, packetId, catalogTrackName)
|
|
2121
2175
|
// A vanished block that never received a symbol holds no decoder state
|
|
@@ -2139,7 +2193,7 @@ export class FECManager {
|
|
|
2139
2193
|
if (workerClient) {
|
|
2140
2194
|
this.#pendingWorkerCleanups.add(key)
|
|
2141
2195
|
this.#pendingWorkerCleanupClients.set(key, workerClient)
|
|
2142
|
-
this.#boundOwedCleanup(key, packetId, workerClient)
|
|
2196
|
+
this.#boundOwedCleanup(key, packetId, workerClient, dueAtMs)
|
|
2143
2197
|
workerClient.cleanup(sbn)
|
|
2144
2198
|
} else {
|
|
2145
2199
|
const decoder = this.#decoderForPacket(packetId, catalogTrackName)?.client
|
|
@@ -2558,6 +2612,7 @@ export class FECManager {
|
|
|
2558
2612
|
resetMap(this.#blockSourceEsis)
|
|
2559
2613
|
resetMap(this.#blockRebuiltEsis)
|
|
2560
2614
|
resetMap(this.#blockAuthoredLengths)
|
|
2615
|
+
resetSet(this.#isoSpanKeys)
|
|
2561
2616
|
resetMap(this.#blockRepairRsIds)
|
|
2562
2617
|
resetMap(this.#blockRsbLengths)
|
|
2563
2618
|
resetMap(this.#heldLayerStates)
|
|
@@ -2691,6 +2746,7 @@ export class FECManager {
|
|
|
2691
2746
|
dropMap(this.#blockSourceEsis)
|
|
2692
2747
|
dropMap(this.#blockRebuiltEsis)
|
|
2693
2748
|
dropMap(this.#blockAuthoredLengths)
|
|
2749
|
+
dropSet(this.#isoSpanKeys)
|
|
2694
2750
|
dropMap(this.#blockRepairRsIds)
|
|
2695
2751
|
dropMap(this.#blockRsbLengths)
|
|
2696
2752
|
dropMap(this.#blockPathCounts)
|
|
@@ -3122,8 +3178,9 @@ export class FECManager {
|
|
|
3122
3178
|
return true
|
|
3123
3179
|
}
|
|
3124
3180
|
/**
|
|
3125
|
-
* Drop a block's repair hold: its FETCH ended without delivering
|
|
3126
|
-
*
|
|
3181
|
+
* Drop a block's repair hold: its FETCH ended without delivering, or brought
|
|
3182
|
+
* back only a repair that cannot complete it. The block falls back to its
|
|
3183
|
+
* multicast wait, which may already have passed.
|
|
3127
3184
|
*/
|
|
3128
3185
|
releaseRepairHold(sbn: number, packetId: number, catalogTrackName?: string): void {
|
|
3129
3186
|
const key = this.#blockKey(sbn, packetId, catalogTrackName)
|
|
@@ -3285,6 +3342,7 @@ export class FECManager {
|
|
|
3285
3342
|
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName)
|
|
3286
3343
|
const evictedBefore = locator.heldEvicted
|
|
3287
3344
|
const held = locator.hold(ssId, source)
|
|
3345
|
+
this.#settleExpiredIsoHoles(locator, packetId, catalogTrackName)
|
|
3288
3346
|
if (held && source.source === 'multicast') this.#noteMulticastSource(locator, ssId, packetId, catalogTrackName)
|
|
3289
3347
|
const evicted = locator.heldEvicted - evictedBefore
|
|
3290
3348
|
if (evicted > 0) {
|
|
@@ -3313,17 +3371,26 @@ export class FECManager {
|
|
|
3313
3371
|
ssStart: number,
|
|
3314
3372
|
ssbLength: number,
|
|
3315
3373
|
): IsoRepairPlacement<IsoHeldSource> {
|
|
3316
|
-
const
|
|
3374
|
+
const locator = this.#growingIsoLocatorFor(packetId, catalogTrackName)
|
|
3375
|
+
const placement = locator.placeRepair(ssStart, ssbLength)
|
|
3376
|
+
this.#settleExpiredIsoHoles(locator, packetId, catalogTrackName)
|
|
3317
3377
|
if (placement.status !== 'placed') {
|
|
3318
3378
|
this.#isoRepairRefusedCount++
|
|
3319
3379
|
if (this.#isoRepairRefusedCount <= 5 || this.#isoRepairRefusedCount % 100 === 0) {
|
|
3320
3380
|
console.warn(`[FECManager] packetId=${packetId} iso-ssbg1-v1 repair refused (${placement.status}): ${placement.reason}`)
|
|
3321
3381
|
}
|
|
3382
|
+
// The repair FETCH of a span's first group brought back a shorter block:
|
|
3383
|
+
// the span holds more than one, so that group cannot complete it, and
|
|
3384
|
+
// holding the span (and every frame behind it) for the FETCH buys
|
|
3385
|
+
// nothing. It falls back to its multicast wait.
|
|
3386
|
+
if (placement.ofSpan !== undefined) this.releaseRepairHold(placement.ofSpan.start, packetId, catalogTrackName)
|
|
3322
3387
|
return placement
|
|
3323
3388
|
}
|
|
3389
|
+
// A repair states the block at its SS_Start: a span the locator kept
|
|
3390
|
+
// there is now that one block (IsoBlockLocator.placeRepair).
|
|
3391
|
+
this.#isoSpanKeys.delete(this.#blockKey(placement.block.start, packetId, catalogTrackName))
|
|
3324
3392
|
if (placement.span !== undefined) this.#openIsoSpan(placement.span, packetId, catalogTrackName)
|
|
3325
3393
|
if (placement.hole !== undefined) {
|
|
3326
|
-
const locator = this.#isoLocatorFor(packetId, catalogTrackName)
|
|
3327
3394
|
const hole = placement.hole.block
|
|
3328
3395
|
const previous = locator.blockEndingAt(hole.start)
|
|
3329
3396
|
const loss: UndelimitedIsoLoss = {
|
|
@@ -3345,18 +3412,38 @@ export class FECManager {
|
|
|
3345
3412
|
* `handler`, instead of a repair-less span; null restores spans. Set by the
|
|
3346
3413
|
* range repair scheduler; applies to every stream from its next gap. The
|
|
3347
3414
|
* handler and the deferral are one setting, so no hole is ever created with
|
|
3348
|
-
* nobody to close it.
|
|
3415
|
+
* nobody to close it. `onExpired` is told of each pending hole the stream
|
|
3416
|
+
* runs past the locator's horizon (BLO-52085): returning true, it closes
|
|
3417
|
+
* the hole (`closeIsoHoles`), so the span it leaves still fails and is
|
|
3418
|
+
* reported; otherwise the hole is discarded with its sources.
|
|
3349
3419
|
*/
|
|
3350
|
-
setUndelimitedLossHandler(
|
|
3420
|
+
setUndelimitedLossHandler(
|
|
3421
|
+
handler: ((loss: UndelimitedIsoLoss) => void) | null,
|
|
3422
|
+
onExpired: ((hole: ExpiredIsoHole) => boolean) | null = null,
|
|
3423
|
+
): void {
|
|
3351
3424
|
this.#onUndelimitedLoss = handler
|
|
3425
|
+
this.#onIsoHoleExpired = handler === null ? null : onExpired
|
|
3352
3426
|
this.#deferIsoGaps = handler !== null
|
|
3353
3427
|
for (const locator of this.#isoLocators.values()) locator.deferIncompleteGaps = this.#deferIsoGaps
|
|
3354
3428
|
}
|
|
3355
3429
|
|
|
3430
|
+
/** Hand each hole the locator's horizon passed to its owner, or discard it (setUndelimitedLossHandler). */
|
|
3431
|
+
#settleExpiredIsoHoles(
|
|
3432
|
+
locator: IsoBlockLocator<IsoHeldSource>,
|
|
3433
|
+
packetId: number,
|
|
3434
|
+
catalogTrackName: string | undefined,
|
|
3435
|
+
): void {
|
|
3436
|
+
for (const hole of locator.takeExpiredHoles()) {
|
|
3437
|
+
const expired: ExpiredIsoHole = { packetId, catalogTrackName, holeStart: hole.start, holeLength: hole.length }
|
|
3438
|
+
if (this.#onIsoHoleExpired?.(expired) !== true) locator.closeHolesWithin(hole.start, hole.length)
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3356
3442
|
/**
|
|
3357
|
-
* The range repair for a hole ended: every hole
|
|
3358
|
-
*
|
|
3359
|
-
* with the held sources they placed, for the caller to feed
|
|
3443
|
+
* The range repair for a hole ended: every hole inside `[start, start +
|
|
3444
|
+
* length)`, pending or expired, becomes a repair-less span. Returns those
|
|
3445
|
+
* spans with the held sources they placed, for the caller to feed
|
|
3446
|
+
* (MmtpRouter).
|
|
3360
3447
|
*/
|
|
3361
3448
|
closeIsoHoles(
|
|
3362
3449
|
packetId: number,
|
|
@@ -3385,7 +3472,10 @@ export class FECManager {
|
|
|
3385
3472
|
catalogTrackName: string | undefined,
|
|
3386
3473
|
): void {
|
|
3387
3474
|
const key = this.#blockKey(span.block.start, packetId, catalogTrackName)
|
|
3388
|
-
if (!this.#blockAuthoredLengths.has(key))
|
|
3475
|
+
if (!this.#blockAuthoredLengths.has(key)) {
|
|
3476
|
+
this.#blockAuthoredLengths.set(key, span.block.length)
|
|
3477
|
+
this.#isoSpanKeys.add(key)
|
|
3478
|
+
}
|
|
3389
3479
|
if (span.placed.length === 0) this.#materializeBlock(span.block.start, packetId, catalogTrackName, true)
|
|
3390
3480
|
}
|
|
3391
3481
|
|
|
@@ -3864,13 +3954,27 @@ export class FECManager {
|
|
|
3864
3954
|
|
|
3865
3955
|
client.onCleanupComplete((sbn: number) => {
|
|
3866
3956
|
if (this.#releasedWorkerClients.has(client)) return
|
|
3867
|
-
const
|
|
3957
|
+
const owed = [...this.#pendingWorkerCleanups].filter((key) => this.#pendingWorkerCleanupClients.get(key) === client)
|
|
3958
|
+
const cleanupKeys = owed.filter((key) => {
|
|
3868
3959
|
const block = this.#parseBlockKey(key)
|
|
3869
|
-
return block.packetId === packetId &&
|
|
3870
|
-
block.sbn === sbn &&
|
|
3871
|
-
this.#pendingWorkerCleanupClients.get(key) === client
|
|
3960
|
+
return block.packetId === packetId && block.sbn === sbn
|
|
3872
3961
|
})
|
|
3873
|
-
|
|
3962
|
+
// A cleanup is a queue-drain barrier: the Worker took every symbol fed
|
|
3963
|
+
// before it was asked for first, so every block already due by then
|
|
3964
|
+
// is settled by this acknowledgement too, not one barrier later. The
|
|
3965
|
+
// client sends one barrier at a time, and the blocks a repair walk
|
|
3966
|
+
// stated in one span are due together (BLO-52085). That holds only
|
|
3967
|
+
// while no block is fed a symbol after its dueAtMs, which is its
|
|
3968
|
+
// receipt-scoped deadline, not when feeding stopped: a symbol fed
|
|
3969
|
+
// between that deadline and the block's timer, after another block's
|
|
3970
|
+
// barrier was asked for, would find the block settled (failed) one
|
|
3971
|
+
// barrier early. Walks end before D-28 (walkDeadlineAt), so that window
|
|
3972
|
+
// is the timer's lateness alone; whatever feeds a block past its dueAtMs
|
|
3973
|
+
// must leave it to its own barrier.
|
|
3974
|
+
const askedAtMs = Math.max(...cleanupKeys.map((key) => this.#pendingWorkerCleanupBounds.get(key)?.askedAtMs ?? -Infinity))
|
|
3975
|
+
const due = owed.filter((key) =>
|
|
3976
|
+
!cleanupKeys.includes(key) && (this.#pendingWorkerCleanupBounds.get(key)?.dueAtMs ?? Infinity) <= askedAtMs)
|
|
3977
|
+
for (const key of [...cleanupKeys, ...due]) this.#settleOwedCleanup(key, 'cleanup-ack')
|
|
3874
3978
|
})
|
|
3875
3979
|
|
|
3876
3980
|
client.onError((msg: string, code?: FecWorkerErrorCode) => {
|
|
@@ -3952,9 +4056,11 @@ export class FECManager {
|
|
|
3952
4056
|
* fail deadline already passed, so failing it is the decision that deadline
|
|
3953
4057
|
* made. A late ack then finds nothing owed and is a no-op.
|
|
3954
4058
|
*/
|
|
3955
|
-
#boundOwedCleanup(key: string, packetId: number, client: FecWorkerClient): void {
|
|
4059
|
+
#boundOwedCleanup(key: string, packetId: number, client: FecWorkerClient, dueAtMs: number): void {
|
|
3956
4060
|
this.#clearOwedCleanupBound(key)
|
|
3957
4061
|
const bound = {
|
|
4062
|
+
dueAtMs,
|
|
4063
|
+
askedAtMs: performance.now(),
|
|
3958
4064
|
handle: setTimeout(() => {
|
|
3959
4065
|
if (this.#pendingWorkerCleanupBounds.get(key) !== bound) return
|
|
3960
4066
|
this.#pendingWorkerCleanupBounds.delete(key)
|
|
@@ -4726,7 +4832,8 @@ export class FECManager {
|
|
|
4726
4832
|
* the inactivity deadline on each symbol, records the symbol's in-band
|
|
4727
4833
|
* classification evidence, and marks the block clean when all of its
|
|
4728
4834
|
* authored source symbols arrived: the SSB_length a repair symbol declared,
|
|
4729
|
-
* otherwise K.
|
|
4835
|
+
* otherwise K. `held` marks a source held unplaced since it arrived and
|
|
4836
|
+
* placed only now (see #noteSbnSeen).
|
|
4730
4837
|
*
|
|
4731
4838
|
* Throws for a packetId that was never configured, so a classified block
|
|
4732
4839
|
* never reads the manager-global K or timeout.
|
|
@@ -4737,6 +4844,7 @@ export class FECManager {
|
|
|
4737
4844
|
catalogTrackName: string | undefined,
|
|
4738
4845
|
esi: number,
|
|
4739
4846
|
evidence: SourceSymbolEvidence,
|
|
4847
|
+
held = false,
|
|
4740
4848
|
): void {
|
|
4741
4849
|
const trackConfig = this.#trackConfigs.get(packetId)
|
|
4742
4850
|
if (trackConfig === undefined) {
|
|
@@ -4758,6 +4866,7 @@ export class FECManager {
|
|
|
4758
4866
|
'source',
|
|
4759
4867
|
evidence.packetSequenceNumber,
|
|
4760
4868
|
Number.isInteger(esi) ? (sbn + esi) >>> 0 : sbn,
|
|
4869
|
+
held,
|
|
4761
4870
|
)
|
|
4762
4871
|
|
|
4763
4872
|
// A source ESI at or past the block's authored SSB_length contradicts it;
|
|
@@ -4837,6 +4946,7 @@ export class FECManager {
|
|
|
4837
4946
|
this.#blockSourceEsis.delete(key)
|
|
4838
4947
|
this.#blockRebuiltEsis.delete(key)
|
|
4839
4948
|
this.#blockAuthoredLengths.delete(key)
|
|
4949
|
+
this.#isoSpanKeys.delete(key)
|
|
4840
4950
|
this.#blockRepairRsIds.delete(key)
|
|
4841
4951
|
this.#blockRsbLengths.delete(key)
|
|
4842
4952
|
// BLO-3323 L15: track which SBNs reached K source-symbol arrivals so the
|
|
@@ -4878,6 +4988,20 @@ export class FECManager {
|
|
|
4878
4988
|
return this.#receiptAtMs ?? performance.now()
|
|
4879
4989
|
}
|
|
4880
4990
|
|
|
4991
|
+
/**
|
|
4992
|
+
* The whole-ms delay of a timer for `deadlineMs` armed inside a receipt
|
|
4993
|
+
* scope: counted from the receipt time, as the timer of a symbol fed when
|
|
4994
|
+
* it was received was, less the whole ms since. So it fires on the tick
|
|
4995
|
+
* that timer would have, never later than a delay counted from now, and
|
|
4996
|
+
* its handler still re-arms a timer that fires before its deadline.
|
|
4997
|
+
* Counted from now, a deadline set from the receipt time is rounded up
|
|
4998
|
+
* from another fraction of a ms (BLO-52085).
|
|
4999
|
+
*/
|
|
5000
|
+
#receiptTimerDelayMs(deadlineMs: number, minMs: number): number {
|
|
5001
|
+
const receivedAtMs = this.#receiptAtMs!
|
|
5002
|
+
return Math.max(minMs, Math.ceil(deadlineMs - receivedAtMs) - Math.ceil(performance.now() - receivedAtMs))
|
|
5003
|
+
}
|
|
5004
|
+
|
|
4881
5005
|
/**
|
|
4882
5006
|
* A held packet admitted late arms from its (earlier) receipt time; that
|
|
4883
5007
|
* never pulls in a deadline later activity already armed. Outside a
|
|
@@ -4969,6 +5093,9 @@ export class FECManager {
|
|
|
4969
5093
|
catalogTrackName?: string,
|
|
4970
5094
|
): void {
|
|
4971
5095
|
const key = this.#blockKey(sbn, packetId, catalogTrackName)
|
|
5096
|
+
const span: IsoSpanFailure | undefined = this.#isoSpanKeys.delete(key)
|
|
5097
|
+
? { length: this.#blockAuthoredLengths.get(key) ?? 0, sources: this.#blockSourceCounts.get(key) ?? 0 }
|
|
5098
|
+
: undefined
|
|
4972
5099
|
this.#traceLifecycle('failed', sbn, packetId, catalogTrackName)
|
|
4973
5100
|
this.#clearRepairDeadline(key)
|
|
4974
5101
|
this.#clearBlockTimer(key)
|
|
@@ -4988,7 +5115,8 @@ export class FECManager {
|
|
|
4988
5115
|
this.#markPathBlockComplete(sbn, packetId, catalogTrackName)
|
|
4989
5116
|
this.#fecStats.blocksFailed++
|
|
4990
5117
|
this.#statsForPacket(packetId).blocksFailed++
|
|
4991
|
-
this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass)
|
|
5118
|
+
if (span === undefined) this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass)
|
|
5119
|
+
else this.onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass, span)
|
|
4992
5120
|
this.onBlockResolved?.(sbn, packetId, catalogTrackName)
|
|
4993
5121
|
}
|
|
4994
5122
|
|
|
@@ -5020,6 +5148,7 @@ export class FECManager {
|
|
|
5020
5148
|
this.#blockSourceEsis.delete(key)
|
|
5021
5149
|
this.#blockRebuiltEsis.delete(key)
|
|
5022
5150
|
this.#blockAuthoredLengths.delete(key)
|
|
5151
|
+
this.#isoSpanKeys.delete(key)
|
|
5023
5152
|
this.#blockRepairRsIds.delete(key)
|
|
5024
5153
|
this.#blockRsbLengths.delete(key)
|
|
5025
5154
|
this.#clearBlockTimer(key)
|
|
@@ -5129,6 +5258,7 @@ export class FECManager {
|
|
|
5129
5258
|
this.#blockSourceEsis.clear()
|
|
5130
5259
|
this.#blockRebuiltEsis.clear()
|
|
5131
5260
|
this.#blockAuthoredLengths.clear()
|
|
5261
|
+
this.#isoSpanKeys.clear()
|
|
5132
5262
|
this.#blockRepairRsIds.clear()
|
|
5133
5263
|
this.#blockRsbLengths.clear()
|
|
5134
5264
|
this.#heldLayerStates.clear()
|