@torrent-tv/proxy 2.104.0 → 2.104.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1520,6 +1520,11 @@ export class TorrentPool {
|
|
|
1520
1520
|
const context = this.#waitTraceByTorrent.get(torrent);
|
|
1521
1521
|
if (context?.trace === true) context.emitWaitTrace?.(context.sourceKey, process, phase, fields);
|
|
1522
1522
|
};
|
|
1523
|
+
if (waitTrace?.trace === true) {
|
|
1524
|
+
// WebTorrent assigns the store during _onStore, after client.add first
|
|
1525
|
+
// returns. Revisit this hook when its ready callback calls us.
|
|
1526
|
+
tracePieceStorePut(pieceStoreOf(torrent), trace);
|
|
1527
|
+
}
|
|
1523
1528
|
const pool = this;
|
|
1524
1529
|
const wrap = (method, process) => {
|
|
1525
1530
|
const original = torrent[method];
|
|
@@ -1564,16 +1569,28 @@ export class TorrentPool {
|
|
|
1564
1569
|
this.#waitTraceHooks.add(torrent);
|
|
1565
1570
|
wrap("_startDiscovery", "W2");
|
|
1566
1571
|
wrap("_onStore", "W6");
|
|
1567
|
-
tracePieceStorePut(pieceStoreOf(torrent), trace);
|
|
1568
1572
|
const verify = torrent._verifyPieces;
|
|
1569
1573
|
if (typeof verify === "function") {
|
|
1570
1574
|
torrent._verifyPieces = function (callback, ...args) {
|
|
1571
1575
|
const startedAt = performance.now();
|
|
1572
1576
|
trace("W5", "begin", { startedAt });
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
+
let finished = false;
|
|
1578
|
+
const finish = (outcome, fields = {}) => {
|
|
1579
|
+
if (finished) return;
|
|
1580
|
+
finished = true;
|
|
1581
|
+
const endedAt = performance.now();
|
|
1582
|
+
trace("W5", "end", { startedAt, endedAt, durationMs: endedAt - startedAt, outcome, ...fields });
|
|
1583
|
+
};
|
|
1584
|
+
try {
|
|
1585
|
+
return verify.call(this, (...results) => {
|
|
1586
|
+
const error = results[0];
|
|
1587
|
+
finish(error ? "error" : "verified", error ? { error: String(error?.message ?? error) } : {});
|
|
1588
|
+
callback?.(...results);
|
|
1589
|
+
}, ...args);
|
|
1590
|
+
} catch (error) {
|
|
1591
|
+
finish("error", { error: String(error?.message ?? error) });
|
|
1592
|
+
throw error;
|
|
1593
|
+
}
|
|
1577
1594
|
};
|
|
1578
1595
|
}
|
|
1579
1596
|
const timing = this.#swarmTimingByTorrent.get(torrent);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/** Record the torrent worker's verified-piece storage write without changing its callback API. */
|
|
2
|
+
const tracedStores = new WeakSet();
|
|
3
|
+
|
|
2
4
|
export function tracePieceStorePut(store, trace, now = () => performance.now()) {
|
|
3
5
|
const put = store?.put;
|
|
4
|
-
if (typeof put !== "function") return false;
|
|
6
|
+
if (!store || tracedStores.has(store) || typeof put !== "function") return false;
|
|
5
7
|
|
|
6
8
|
store.put = function (index, bytes, callback) {
|
|
7
9
|
const startedAt = now();
|
|
@@ -24,5 +26,6 @@ export function tracePieceStorePut(store, trace, now = () => performance.now())
|
|
|
24
26
|
throw error;
|
|
25
27
|
}
|
|
26
28
|
};
|
|
29
|
+
tracedStores.add(store);
|
|
27
30
|
return true;
|
|
28
31
|
}
|
|
@@ -494,46 +494,63 @@ async function runCommand(command, params, id) {
|
|
|
494
494
|
const statsStartedAt = performance.now();
|
|
495
495
|
emitWaitTrace(params.sourceKey, "W10", "begin", { startedAt: statsStartedAt,
|
|
496
496
|
fileIndex: params.fileIndex, forecast: params.forecast === true });
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
endedAt: performance.now(), outcome: whole ? "whole-file" : "pending", fileIndex: params.fileIndex });
|
|
502
|
-
return whole ? wholeFileStats(whole.length) : { pending: true };
|
|
503
|
-
}
|
|
504
|
-
// What this file's own interruptions demand, measured by the reader in
|
|
505
|
-
// this thread. It travels with the stats because the caller asking for
|
|
506
|
-
// them is the one that has to decide with them — the browser's smallest
|
|
507
|
-
// safe buffer, and the speed a quality step must sustain. Null until a
|
|
508
|
-
// second interruption has been seen: one wait shows no interval, and an
|
|
509
|
-
// interval invented from one point is exactly what this work removes.
|
|
510
|
-
const file = Array.isArray(torrent?.files) ? torrent.files[params.fileIndex] : null;
|
|
511
|
-
const forecastAt = performance.now();
|
|
512
|
-
const forecast = file && params.forecast === true ? downloadForecastOf(torrent) : null;
|
|
513
|
-
const forecastMs = params.forecast === true ? performance.now() - forecastAt : 0;
|
|
514
|
-
const stats = pool.getFileStats(torrent, params.fileIndex);
|
|
515
|
-
const ranges = [];
|
|
516
|
-
if (forecast) for (const [piece, availableAt] of forecast.arrivals) {
|
|
517
|
-
const start = piece * torrent.pieceLength;
|
|
518
|
-
const end = Math.min(torrent.length, (piece + 1) * torrent.pieceLength);
|
|
519
|
-
if (end > start) ranges.push({ start, end, availableAt });
|
|
520
|
-
}
|
|
521
|
-
emitWaitTrace(params.sourceKey, "W10", "end", { startedAt: statsStartedAt,
|
|
522
|
-
endedAt: performance.now(), outcome: "ok", fileIndex: params.fileIndex,
|
|
523
|
-
forecast: params.forecast === true });
|
|
524
|
-
return {
|
|
525
|
-
...stats,
|
|
526
|
-
...(params.forecast === true ? { downloadForecast: {
|
|
527
|
-
measuredAt: forecast?.measuredAt ?? Date.now(), global: true, ranges
|
|
528
|
-
} } : {}),
|
|
529
|
-
supply: supplyFiguresFor(torrent?.infoHash, file?.name, params.segmentSeconds ?? 4),
|
|
530
|
-
// How long this answer waited for the arrival forecast, which the
|
|
531
|
-
// caller says beside its own time (ViewerRequests, torrent-tv/meta#178).
|
|
532
|
-
forecastMs
|
|
497
|
+
const finishStatsTrace = (outcome, fields = {}) => {
|
|
498
|
+
const endedAt = performance.now();
|
|
499
|
+
emitWaitTrace(params.sourceKey, "W10", "end", { startedAt: statsStartedAt, endedAt,
|
|
500
|
+
durationMs: endedAt - statsStartedAt, outcome, fileIndex: params.fileIndex, ...fields });
|
|
533
501
|
};
|
|
502
|
+
try {
|
|
503
|
+
const torrent = await knownTorrent(params.sourceKey);
|
|
504
|
+
if (!torrent) {
|
|
505
|
+
const whole = wholeSources.fileOf(params.sourceKey, params.fileIndex);
|
|
506
|
+
finishStatsTrace(whole ? "whole-file" : "pending");
|
|
507
|
+
return whole ? wholeFileStats(whole.length) : { pending: true };
|
|
508
|
+
}
|
|
509
|
+
// What this file's own interruptions demand, measured by the reader in
|
|
510
|
+
// this thread. It travels with the stats because the caller asking for
|
|
511
|
+
// them is the one that has to decide with them — the browser's smallest
|
|
512
|
+
// safe buffer, and the speed a quality step must sustain. Null until a
|
|
513
|
+
// second interruption has been seen: one wait shows no interval, and an
|
|
514
|
+
// interval invented from one point is exactly what this work removes.
|
|
515
|
+
const file = Array.isArray(torrent?.files) ? torrent.files[params.fileIndex] : null;
|
|
516
|
+
const forecastAt = performance.now();
|
|
517
|
+
const forecast = file && params.forecast === true ? downloadForecastOf(torrent) : null;
|
|
518
|
+
const forecastMs = params.forecast === true ? performance.now() - forecastAt : 0;
|
|
519
|
+
const stats = pool.getFileStats(torrent, params.fileIndex);
|
|
520
|
+
const ranges = [];
|
|
521
|
+
if (forecast) for (const [piece, availableAt] of forecast.arrivals) {
|
|
522
|
+
const start = piece * torrent.pieceLength;
|
|
523
|
+
const end = Math.min(torrent.length, (piece + 1) * torrent.pieceLength);
|
|
524
|
+
if (end > start) ranges.push({ start, end, availableAt });
|
|
525
|
+
}
|
|
526
|
+
finishStatsTrace("ok", { forecast: params.forecast === true });
|
|
527
|
+
return {
|
|
528
|
+
...stats,
|
|
529
|
+
...(params.forecast === true ? { downloadForecast: {
|
|
530
|
+
measuredAt: forecast?.measuredAt ?? Date.now(), global: true, ranges
|
|
531
|
+
} } : {}),
|
|
532
|
+
supply: supplyFiguresFor(torrent?.infoHash, file?.name, params.segmentSeconds ?? 4),
|
|
533
|
+
// How long this answer waited for the arrival forecast, which the
|
|
534
|
+
// caller says beside its own time (ViewerRequests, torrent-tv/meta#178).
|
|
535
|
+
forecastMs
|
|
536
|
+
};
|
|
537
|
+
} catch (error) {
|
|
538
|
+
finishStatsTrace("error", { error: String(error?.message ?? error) });
|
|
539
|
+
throw error;
|
|
540
|
+
}
|
|
534
541
|
}
|
|
535
542
|
|
|
536
543
|
case Command.PRIORITY_MAP: {
|
|
544
|
+
const startedAt = performance.now();
|
|
545
|
+
emitWaitTrace(params.sourceKey, "W7", "begin", { startedAt, fileIndex: params.fileIndex });
|
|
546
|
+
let traceFinished = false;
|
|
547
|
+
const finishPriorityTrace = (outcome, fields = {}) => {
|
|
548
|
+
if (traceFinished) return;
|
|
549
|
+
traceFinished = true;
|
|
550
|
+
const endedAt = performance.now();
|
|
551
|
+
emitWaitTrace(params.sourceKey, "W7", "end", { startedAt, endedAt,
|
|
552
|
+
durationMs: endedAt - startedAt, outcome, fileIndex: params.fileIndex, ...fields });
|
|
553
|
+
};
|
|
537
554
|
// When this handler began, on the clock the main thread posted by: the
|
|
538
555
|
// post to here holds the transfer and this thread's queue together
|
|
539
556
|
// (torrent-tv/meta#177).
|
|
@@ -542,20 +559,23 @@ async function runCommand(command, params, id) {
|
|
|
542
559
|
// only update an existing source, never revive one after its viewer left.
|
|
543
560
|
const zones = Array.isArray(params.zones) ? params.zones : [];
|
|
544
561
|
// Nor may one for a file held whole, which has nothing left to fetch.
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
562
|
+
try {
|
|
563
|
+
const torrent = await knownTorrent(params.sourceKey);
|
|
564
|
+
if (!torrent) {
|
|
565
|
+
finishPriorityTrace("source-not-ready");
|
|
566
|
+
return { appliedMs: null, receivedAt, repliedAt: performance.timeOrigin + performance.now() };
|
|
567
|
+
}
|
|
568
|
+
// What applying a map costs this thread, told to whoever sent it: every
|
|
569
|
+
// zone becomes a window of the register and the swarm is told again
|
|
570
|
+
// (torrent-tv/meta#166).
|
|
571
|
+
ensureArrivalsWired(params.sourceKey, torrent);
|
|
572
|
+
pool.applyPriorityMap(torrent, params.fileIndex, zones, params.durationSeconds);
|
|
573
|
+
torrent.emit("priority-map-changed", params.fileIndex);
|
|
574
|
+
finishPriorityTrace("applied");
|
|
575
|
+
} catch (error) {
|
|
576
|
+
finishPriorityTrace("error", { error: String(error?.message ?? error) });
|
|
577
|
+
throw error;
|
|
548
578
|
}
|
|
549
|
-
// What applying a map costs this thread, told to whoever sent it: every
|
|
550
|
-
// zone becomes a window of the register and the swarm is told again
|
|
551
|
-
// (torrent-tv/meta#166).
|
|
552
|
-
const startedAt = performance.now();
|
|
553
|
-
emitWaitTrace(params.sourceKey, "W7", "begin", { fileIndex: params.fileIndex });
|
|
554
|
-
ensureArrivalsWired(params.sourceKey, torrent);
|
|
555
|
-
pool.applyPriorityMap(torrent, params.fileIndex, zones, params.durationSeconds);
|
|
556
|
-
torrent.emit("priority-map-changed", params.fileIndex);
|
|
557
|
-
emitWaitTrace(params.sourceKey, "W7", "end", { startedAt, endedAt: performance.now(),
|
|
558
|
-
fileIndex: params.fileIndex });
|
|
559
579
|
return { appliedMs: performance.now() - startedAt, receivedAt, repliedAt: performance.timeOrigin + performance.now() };
|
|
560
580
|
}
|
|
561
581
|
|