@torrent-tv/proxy 2.95.27 → 2.95.28
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
|
@@ -18,12 +18,10 @@
|
|
|
18
18
|
* So distinct non-zero numbers give an order once and a round robin thereafter.
|
|
19
19
|
* Only two things hold: non-zero rotates fairly, zero is always last.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* spend the shared link on it. A permanently low priority does exactly that,
|
|
26
|
-
* which is why it is not what this does.
|
|
21
|
+
* WebTorrent checks selections per wire and falls through to zero-priority
|
|
22
|
+
* selections when a wire has no piece from a non-zero selection. Therefore the
|
|
23
|
+
* global first class is the only urgent level stated while any urgent piece is
|
|
24
|
+
* missing. Conditional levels are stated only when no urgent piece is missing.
|
|
27
25
|
*
|
|
28
26
|
* The rotation is not merely tolerated, it is wanted: with two viewers of one
|
|
29
27
|
* film both stopped, both their needs sit at {@link Urgency.BLOCKED} and the
|
|
@@ -107,10 +105,10 @@ export function isConditional(urgency) {
|
|
|
107
105
|
/**
|
|
108
106
|
* The number handed to `torrent.select`.
|
|
109
107
|
*
|
|
110
|
-
* Two values only, because two is all the library keeps:
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
108
|
+
* Two values only, because two is all the library keeps: selected first-class
|
|
109
|
+
* windows share one non-zero priority and rotate fairly among themselves;
|
|
110
|
+
* conditional levels take zero. Less urgent windows are withheld while an
|
|
111
|
+
* urgent piece is missing, rather than relying on zero to keep them back.
|
|
114
112
|
*
|
|
115
113
|
* @param {number} urgency
|
|
116
114
|
* @returns {number}
|
|
@@ -17,23 +17,17 @@
|
|
|
17
17
|
* vendored 2.8.5: selections are sorted by priority only when one is inserted,
|
|
18
18
|
* and `shufflePriority` then moves the selection just served to the back of the
|
|
19
19
|
* whole non-zero group. Distinct numbers therefore order the list once and
|
|
20
|
-
* round-robin it afterwards. The library
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* round-robin it afterwards. The library checks selections separately for each
|
|
21
|
+
* wire. If that wire holds no missing piece from a non-zero selection, it falls
|
|
22
|
+
* through to a zero selection. A zero priority therefore does not withhold a
|
|
23
|
+
* less urgent piece from a peer that lacks the first class.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* request: on Home Assistant, with three peers each holding every third piece
|
|
32
|
-
* and one complete slow peer, twelve segments took 184 s at 0.61 MiB/s against
|
|
33
|
-
* 18.3 s at 6.1 MiB/s when the whole band was stated
|
|
34
|
-
* (`research/download-translation-2026-10-07.md`, torrent-tv/meta#95).
|
|
35
|
-
* Speculative levels stay withheld while anything urgent is missing anywhere,
|
|
36
|
-
* because the link is shared.
|
|
25
|
+
* While an urgent piece is missing, only the global first class is stated.
|
|
26
|
+
* This keeps a wire that lacks those pieces from spending its requests on a
|
|
27
|
+
* lower class. The first class is the most urgent missing level at its highest
|
|
28
|
+
* map priority, regardless of deadlines. Every window in that class is stated
|
|
29
|
+
* so peers holding different first-class pieces can all contribute. Once no
|
|
30
|
+
* urgent piece is missing, conditional levels may be stated at zero.
|
|
37
31
|
*/
|
|
38
32
|
|
|
39
33
|
import {
|
|
@@ -90,16 +84,18 @@ export class SwarmSelection {
|
|
|
90
84
|
reconcile({ speculativeAllowed = !this.hasUrgentMissing(), firstClass = null } = {}) {
|
|
91
85
|
const missing = this.#register.windows().filter(window => !this.#isSatisfied(window));
|
|
92
86
|
const first = firstClass ?? this.missingBand();
|
|
87
|
+
const strictFirstClass = first !== null && !isConditional(first.urgency);
|
|
93
88
|
/** @type {Map<string, { from: number, to: number, priority: number }>} */
|
|
94
89
|
const wanted = new Map();
|
|
95
90
|
for (const priority of [1, 0]) {
|
|
96
91
|
const ranges = [];
|
|
97
92
|
for (const window of missing) {
|
|
98
93
|
if (!speculativeAllowed && isConditional(window.urgency)) continue;
|
|
99
|
-
//
|
|
100
|
-
//
|
|
94
|
+
// The first class is the only eligible urgent level; zero priority
|
|
95
|
+
// still falls through on wires that have none of its pieces.
|
|
101
96
|
const inFirstClass = first !== null && !isConditional(window.urgency)
|
|
102
97
|
&& window.urgency === first.urgency && window.priority >= first.priority;
|
|
98
|
+
if (strictFirstClass && !inFirstClass) continue;
|
|
103
99
|
if ((inFirstClass ? 1 : 0) !== priority) continue;
|
|
104
100
|
const range = this.#piecesFor(window);
|
|
105
101
|
if (range) ranges.push(range);
|
|
@@ -125,7 +125,7 @@ export function reconcileAll() {
|
|
|
125
125
|
const entries = [...live];
|
|
126
126
|
const speculativeAllowed = !entries.some((entry) => entry.selection.hasUrgentMissing());
|
|
127
127
|
// The first class across every torrent: the most urgent missing level and
|
|
128
|
-
// its highest priority.
|
|
128
|
+
// its highest priority. Lower classes stay out of every torrent's selection.
|
|
129
129
|
const bands = entries.map(entry => entry.selection.missingBand()).filter(Boolean);
|
|
130
130
|
const urgency = bands.length ? Math.min(...bands.map(band => band.urgency)) : null;
|
|
131
131
|
const firstClass = urgency === null ? null
|
|
@@ -142,8 +142,8 @@ export function reconcileAll() {
|
|
|
142
142
|
} finally {
|
|
143
143
|
publishingSelections = false;
|
|
144
144
|
}
|
|
145
|
-
// What the swarm is told to fetch FIRST, said when it changes
|
|
146
|
-
//
|
|
145
|
+
// What the swarm is told to fetch FIRST, said when it changes. Lower classes
|
|
146
|
+
// are withheld because a wire with no first-class piece would fall through.
|
|
147
147
|
const said = entries.map(entry => `${String(entry.torrent.infoHash ?? "").slice(0, 8)}: ` +
|
|
148
148
|
`${(entry.selection.firstClassPieces ?? []).slice(0, 8).join(",") || "nothing"}` +
|
|
149
149
|
`${(entry.selection.firstClassPieces?.length ?? 0) > 8 ? ` +${entry.selection.firstClassPieces.length - 8} more` : ""}`).join("; ");
|
|
@@ -283,11 +283,36 @@ function describeSwarmDemand(torrent) {
|
|
|
283
283
|
? torrent._critical.reduce((count, flag) => (flag ? count + 1 : count), 0)
|
|
284
284
|
: 0;
|
|
285
285
|
|
|
286
|
+
const firstClassPieces = [];
|
|
287
|
+
const seenFirstClassPieces = new Set();
|
|
288
|
+
let omittedFirstClassPieces = 0;
|
|
289
|
+
for (const item of items) {
|
|
290
|
+
if (!(Number(item?.priority) > 0)) continue;
|
|
291
|
+
const from = Number(item?.from);
|
|
292
|
+
const to = Number(item?.to);
|
|
293
|
+
if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to)) continue;
|
|
294
|
+
for (let index = from; index <= to; index += 1) {
|
|
295
|
+
if (seenFirstClassPieces.has(index) || torrent.bitfield?.get(index)) continue;
|
|
296
|
+
seenFirstClassPieces.add(index);
|
|
297
|
+
if (firstClassPieces.length >= 32) {
|
|
298
|
+
omittedFirstClassPieces += 1;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
const holders = wires.filter(wire => !wire?.destroyed && wire?.peerPieces?.get?.(index) === true);
|
|
302
|
+
const ready = holders.filter(wire => wire.peerChoking !== true).length;
|
|
303
|
+
const requests = wires.reduce((count, wire) => count + (Array.isArray(wire?.requests)
|
|
304
|
+
? wire.requests.filter(request => request?.piece === index).length : 0), 0);
|
|
305
|
+
firstClassPieces.push(`${index}(holders=${holders.length},unchoked=${ready},requests=${requests})`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
286
309
|
return (
|
|
287
310
|
`${items.length} selection(s) covering ${selectedPieces} piece(s), ` +
|
|
288
311
|
`${missingSelected} of them missing, ${critical} marked critical; ` +
|
|
289
312
|
`${wires.length} peers, ${interested} we want data from, ${choking} choking us, ` +
|
|
290
|
-
`${asking} being asked, ${inFlight} blocks in flight`
|
|
313
|
+
`${asking} being asked, ${inFlight} blocks in flight; first-class missing ` +
|
|
314
|
+
`${firstClassPieces.join(", ") || "none"}` +
|
|
315
|
+
(omittedFirstClassPieces > 0 ? `; ${omittedFirstClassPieces} more first-class piece(s)` : "")
|
|
291
316
|
);
|
|
292
317
|
}
|
|
293
318
|
|
|
@@ -1183,10 +1208,10 @@ export class TorrentPool {
|
|
|
1183
1208
|
* line of the log said anything was wrong — the collapse had to be recovered
|
|
1184
1209
|
* afterwards by hand from three unrelated counters.
|
|
1185
1210
|
*
|
|
1186
|
-
* So the stall reports itself, and it reports the
|
|
1187
|
-
*
|
|
1188
|
-
* and
|
|
1189
|
-
*
|
|
1211
|
+
* So the stall reports itself, and it reports whether the swarm was asked for
|
|
1212
|
+
* anything (pieces selected and still missing, blocks in flight) or was asked
|
|
1213
|
+
* and did not answer (peer holders and outstanding requests for each missing
|
|
1214
|
+
* first-class piece, plus how many peers are choking us).
|
|
1190
1215
|
*
|
|
1191
1216
|
* @returns {void}
|
|
1192
1217
|
*/
|