@torrent-tv/proxy 2.80.12 → 2.80.13

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.
@@ -1,4 +1,5 @@
1
1
  import { logger } from "../../../utils/logger.js";
2
+ import { bandOf, waits } from "../../../services/priority/WaitLedger.js";
2
3
 
3
4
  /**
4
5
  * How long a request for a not-yet-produced file is held before answering with
@@ -99,7 +100,20 @@ export async function serveSessionFile(req, reply, { hlsSessionManager, sessionI
99
100
  const outcome = clientAborted
100
101
  ? "client-aborted"
101
102
  : result.kind === "ok" ? "served" : result.kind;
102
- logger.info(`[hold] ${fileName} ${outcome} after ${heldMs}ms`);
103
+ // AGAINST THE RANK THE MAP GAVE IT. A wait is the only thing that says
104
+ // whether the prioritisation is being used well, and this is the one place
105
+ // in the proxy where a viewer is measurably waiting for a named segment.
106
+ // Recorded even when the segment was served at once: a run of short waits
107
+ // at the top rank is what "the urgent zone is being served first" looks
108
+ // like, and without them the table would hold only the failures.
109
+ const ranked = result.ranked ?? null;
110
+ if (ranked) {
111
+ waits.note(ranked.address, heldMs, ranked.rank, ranked.topRank);
112
+ }
113
+ logger.info(
114
+ `[hold] ${fileName} ${outcome} after ${heldMs}ms` +
115
+ (ranked ? ` (the map wants it ${bandOf(ranked.rank, ranked.topRank)}, rank ${ranked.rank} of ${ranked.topRank})` : "")
116
+ );
103
117
  }
104
118
 
105
119
  if (result.kind === "not-found") {
@@ -180,6 +194,8 @@ export async function waitForSessionFile(hlsSessionManager, sessionId, fileName,
180
194
  // this wait, and the segment the viewer wanted took 15 ms once it was asked
181
195
  // for.
182
196
  let seekEpoch = hlsSessionManager.seekEpoch(sessionId);
197
+ /** @type {{ address: string, rank: number, topRank: number } | null} */
198
+ let lastRanked = null;
183
199
  while (Date.now() - startedAt < timeoutMs) {
184
200
  const result = await hlsSessionManager.getFileStream(sessionId, fileName, {
185
201
  requestSeq,
@@ -202,9 +218,13 @@ export async function waitForSessionFile(hlsSessionManager, sessionId, fileName,
202
218
  );
203
219
  seekEpoch = hlsSessionManager.seekEpoch(sessionId);
204
220
  }
221
+ // The rank the map last gave this segment, so a request that runs out of
222
+ // patience is still counted against what it was promised. Kept across the
223
+ // polls because the timeout path has no result of its own.
224
+ lastRanked = result.ranked ?? lastRanked;
205
225
  await delay(300);
206
226
  }
207
- return { kind: "warming-up" };
227
+ return { kind: "warming-up", ranked: lastRanked };
208
228
  }
209
229
 
210
230
  /**
@@ -58,4 +58,38 @@ export class SegmentDemand {
58
58
  mapOn(address) {
59
59
  return this.#maps.get(address) ?? [];
60
60
  }
61
+
62
+ /**
63
+ * What the map says about one segment, and about the map it sits in.
64
+ *
65
+ * Asked by whoever MEASURES how well the map is being served: a wait means
66
+ * one thing at the top rank and another at the bottom, and telling them apart
67
+ * needs both the segment's own rank and the highest rank stated — a rank is a
68
+ * position in this map, not an absolute number.
69
+ *
70
+ * AND AN EMPTY MAP IS NOT AN ANSWER, which is why the top rank comes back even
71
+ * when the segment's own is zero. A top rank of zero says the map has not been
72
+ * built yet — a session created a moment ago, before the first pass — and that
73
+ * is a different thing from "nobody is coming". Conflated, every request
74
+ * behind the run reads as absent for as long as a fresh session has no map.
75
+ *
76
+ * @param {string} address
77
+ * @param {number} index
78
+ * @returns {{ rank: number, topRank: number }} Rank zero for a number in
79
+ * nobody's zone, which is a statement: nothing is coming for it.
80
+ */
81
+ rankOf(address, index) {
82
+ const zones = this.mapOn(address);
83
+ let rank = 0;
84
+ let topRank = 0;
85
+ for (const zone of zones) {
86
+ if (zone.priority > topRank) {
87
+ topRank = zone.priority;
88
+ }
89
+ if (index >= zone.from && index <= zone.to && zone.priority > rank) {
90
+ rank = zone.priority;
91
+ }
92
+ }
93
+ return { rank, topRank };
94
+ }
61
95
  }
@@ -9648,6 +9648,8 @@ export class HlsSessionManager {
9648
9648
 
9649
9649
 
9650
9650
  #holdForProduction(session, fileName, isPlaylist, options) {
9651
+ /** @type {{ address: string, rank: number, topRank: number } | null} */
9652
+ let ranked = null;
9651
9653
  if (!isPlaylist) {
9652
9654
  this.#explainHold(session, fileName, "the file is not on disk");
9653
9655
  }
@@ -9667,20 +9669,16 @@ export class HlsSessionManager {
9667
9669
  // failed did the player move to the segment it actually needed — 63 s of
9668
9670
  // spinner after a track that had been made ready in 7.
9669
9671
  //
9670
- // WHETHER ANYBODY IS COMING FOR IT, asked of the priority map rather than
9671
- // measured in segments. The distance used to decide, against the reach of
9672
- // a repair that moved the encoder from a request both are gone. A
9673
- // number inside somebody's zone will be made, so holding it is holding it
9674
- // for a viewer; a number in nobody's zone will not, and holding it spends
9675
- // the player's patience for nothing.
9676
- // AN EMPTY MAP IS NOT AN ANSWER. It says the map has not been built yet —
9677
- // a session created a moment ago, before the first pass — and that is a
9678
- // different thing from "nobody is coming", which is what this refusal
9679
- // needs. Told apart, because conflating them answers every behind-head
9680
- // request as absent for as long as a fresh session has no map.
9681
- const zones = this.encodeOrchestrator.demand.mapOn(session.outputKey ?? "");
9682
- const nobodyIsComing = zones.length > 0 &&
9683
- !zones.some((zone) => requestedIndex >= zone.from && requestedIndex <= zone.to);
9672
+ // WHETHER ANYBODY IS COMING FOR IT, asked of the map (`SegmentDemand.rankOf`,
9673
+ // which also tells "in nobody's zone" from "no map yet"). The same walk
9674
+ // was written out here and could answer only yes or no, so the RANK was
9675
+ // discarded at the one point where a viewer measurably waits for a named
9676
+ // segment; it goes back out with the answer, because the wait is measured
9677
+ // by whoever holds the request.
9678
+ const address = session.outputKey ?? "";
9679
+ const { rank, topRank } = this.encodeOrchestrator.demand.rankOf(address, requestedIndex);
9680
+ ranked = { address, rank, topRank };
9681
+ const nobodyIsComing = topRank > 0 && rank === 0;
9684
9682
  if (
9685
9683
  Number.isFinite(requestedIndex) &&
9686
9684
  requestedIndex < (earliestRunStart(session) ?? 0) &&
@@ -9691,7 +9689,7 @@ export class HlsSessionManager {
9691
9689
  `transcode ${session.id} segment #${requestedIndex} is ${(earliestRunStart(session) ?? 0) - requestedIndex} ` +
9692
9690
  "segments behind the run and in nobody's zone; answered as absent rather than held"
9693
9691
  );
9694
- return { kind: "not-found" };
9692
+ return { kind: "not-found", ranked };
9695
9693
  }
9696
9694
  this.#ensureEncodingFor(
9697
9695
  session,
@@ -9699,7 +9697,7 @@ export class HlsSessionManager {
9699
9697
  Number.isFinite(options?.requestSeq) ? options.requestSeq : Number.MAX_SAFE_INTEGER
9700
9698
  );
9701
9699
  }
9702
- return { kind: "warming-up" };
9700
+ return { kind: "warming-up", ranked };
9703
9701
  }
9704
9702
 
9705
9703
  /**