@torrent-tv/proxy 2.80.12 → 2.80.14

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
  /**
@@ -206,9 +206,25 @@ export class SwarmSelection {
206
206
  * rebuild the second. Now there is one statement and two views of it, both
207
207
  * computed here.
208
208
  *
209
- * Only the urgent levels. Memory holds what will be READ soon; the tail and
210
- * the gap behind the playhead are fetched speculatively and must not push a
211
- * piece the decoder is about to want out of memory.
209
+ * WHAT WILL BE READ SOON, WHICH IS NOT WHAT WILL BE DOWNLOADED SOON. Memory
210
+ * holds the first; the swarm is told the second; and the priority map states
211
+ * the second, over the whole rest of the film.
212
+ *
213
+ * `AHEAD` used to reach memory, and it is exactly the speculative lead: the
214
+ * map states one claimant per zone, so on a film with seven zones one `NEAR`
215
+ * and four `AHEAD` arrived here as five separate holders, each covering tens
216
+ * of megabytes. Field 2026-09-08: the store reported `5 reader(s) want 24
217
+ * piece(s) of 25 the store may hold (widest window 17)` — the union of what
218
+ * was declared equalled the whole capacity, so every admission had to evict a
219
+ * piece somebody had declared, and 100 of 1395 evictions did. Beside that,
220
+ * 6565 spills and 7575 revivals in 44 minutes with a median 0.0 s on disk, and
221
+ * 2138 h264 parse errors on a picture that was being COPIED.
222
+ *
223
+ * Raising the allowance does not touch it: a lead stated over the rest of the
224
+ * film grows to fill whatever memory it is given, and the ratio is unchanged.
225
+ * What belongs in memory is what a READ is stopped on and the little in front
226
+ * of it — the levels a read itself states — and those are `BLOCKED` and
227
+ * `NEAR`.
212
228
  *
213
229
  * @returns {void}
214
230
  */
@@ -218,7 +234,7 @@ export class SwarmSelection {
218
234
  return;
219
235
  }
220
236
  const holding = new Set();
221
- for (const urgency of [Urgency.BLOCKED, Urgency.NEAR, Urgency.AHEAD]) {
237
+ for (const urgency of [Urgency.BLOCKED, Urgency.NEAR]) {
222
238
  for (const window of this.#register.at(urgency)) {
223
239
  const range = this.#piecesFor(window);
224
240
  if (!range) {
@@ -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
  }