@torrent-tv/proxy 2.97.18 → 2.97.20
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 +1 -1
- package/server.js +8 -6
- package/services/encode/AdmittedInput.js +19 -3
- package/services/encode/EncodeRuns.js +1 -1
- package/utils/take-turns.js +28 -0
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -79,6 +79,7 @@ import { readHostFingerprint } from "./services/encode/fingerprint.js";
|
|
|
79
79
|
import { measureStartAndStop } from "./services/encode/start-stop-cost.js";
|
|
80
80
|
import { benchmarkAudio } from "./services/encode/audio-calibration.js";
|
|
81
81
|
import { logger } from "./utils/logger.js";
|
|
82
|
+
import { takeTurns } from "./utils/take-turns.js";
|
|
82
83
|
import { completedFilesRoot } from "./services/storage/files/CompletedFiles.js";
|
|
83
84
|
import { startupCpuProfile } from "./services/storage/cpu-profile.js";
|
|
84
85
|
import { startProcessorReport } from "./services/storage/processor-report.js";
|
|
@@ -532,6 +533,7 @@ export async function startProxyServer({
|
|
|
532
533
|
// answers; asking every segment held the main thread for 3.3 s on a cold
|
|
533
534
|
// two-hour AVI (torrent-tv/meta#178).
|
|
534
535
|
const waiting = new Set();
|
|
536
|
+
const turn = takeTurns();
|
|
535
537
|
for (const zone of demands) {
|
|
536
538
|
if (!map.isCurrent()) return [];
|
|
537
539
|
// WHAT A SEGMENT READS IS WORKED OUT ONCE, not on every publication: its
|
|
@@ -571,14 +573,14 @@ export async function startProxyServer({
|
|
|
571
573
|
value = new SegmentInputs({ index, tracks: wanted }).forInterval({ ...interval, mode: track => modes.get(track) });
|
|
572
574
|
}
|
|
573
575
|
} catch { value = null; }
|
|
574
|
-
//
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
//
|
|
576
|
+
// The pass shares the thread in equal turns (`takeTurns`): in one
|
|
577
|
+
// stretch the first pass over a film's 4000 zones held the main thread
|
|
578
|
+
// for all of its work, and handed back after every zone it took
|
|
579
|
+
// 14.7-22 s while the start forecast waited for it. What was worked
|
|
580
|
+
// out is kept, so a pass a newer map ends is not lost
|
|
578
581
|
// (torrent-tv/meta#178).
|
|
579
582
|
if (value?.kind === "result") segmentSourceRanges.set(rangesKey, value.ranges);
|
|
580
|
-
await
|
|
581
|
-
if (!map.isCurrent()) return [];
|
|
583
|
+
if (await turn() && !map.isCurrent()) return [];
|
|
582
584
|
if (value?.kind === "result") {
|
|
583
585
|
convert(value.ranges);
|
|
584
586
|
continue;
|
|
@@ -99,8 +99,17 @@ export async function admitInput({ sources, reserve, readRanges }) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
/**
|
|
103
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Respect stdin backpressure and release input on every completion path.
|
|
104
|
+
*
|
|
105
|
+
* `waiting(true)` and `waiting(false)` bracket each wait for the next input:
|
|
106
|
+
* the encoder has nothing to read then, and that time is not its work. Counted
|
|
107
|
+
* as work, a soundtrack copy fed a segment at a time read 0.43x while ffmpeg
|
|
108
|
+
* used 0.014 s of processor per second of film, and the start forecast charged
|
|
109
|
+
* the download a second time through it (Home Assistant 2026-10-12,
|
|
110
|
+
* torrent-tv/meta#178).
|
|
111
|
+
*/
|
|
112
|
+
export async function writeAdmittedInput(input, stdin, { next = null, waiting = null } = {}) {
|
|
104
113
|
if (input?.kind !== "result") throw new TypeError("Only complete input may be written to an encoder.");
|
|
105
114
|
const originSeconds = input.originSeconds;
|
|
106
115
|
const declarations = JSON.stringify(input.tracks.map(input => matroskaTrackIdentity(input.track)));
|
|
@@ -137,7 +146,14 @@ export async function writeAdmittedInput(input, stdin, { next = null } = {}) {
|
|
|
137
146
|
}
|
|
138
147
|
await flush();
|
|
139
148
|
current.release();
|
|
140
|
-
|
|
149
|
+
if (next) {
|
|
150
|
+
waiting?.(true);
|
|
151
|
+
try {
|
|
152
|
+
current = await next();
|
|
153
|
+
} finally {
|
|
154
|
+
waiting?.(false);
|
|
155
|
+
}
|
|
156
|
+
} else current = null;
|
|
141
157
|
includeHeader = false;
|
|
142
158
|
}
|
|
143
159
|
stdin.end();
|
|
@@ -1206,7 +1206,7 @@ export class EncodeRuns {
|
|
|
1206
1206
|
run.admittedInputKeys.set(nextIndex, this.#inputFailures.key(next, inputParameters));
|
|
1207
1207
|
run.to = nextIndex++;
|
|
1208
1208
|
return next;
|
|
1209
|
-
} }).catch(error => {
|
|
1209
|
+
}, waiting: waits => { if (waits) run.inputWaitBegins(); else run.inputWaitEnds(); } }).catch(error => {
|
|
1210
1210
|
this.#host.logger.warn(`encode input output=${session.outputKey} segment=${safeIndex} write failed: ${error.message}`);
|
|
1211
1211
|
run.process?.stdin?.destroy();
|
|
1212
1212
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Long work on one thread, shared with the rest of that thread in equal turns.
|
|
3
|
+
*
|
|
4
|
+
* The returned function is called between steps of the work. It hands the
|
|
5
|
+
* thread back once the work has run, since it last did, as long as the rest
|
|
6
|
+
* of the thread took in its own turn then: the work takes at most about twice
|
|
7
|
+
* its own time, and nothing else waits longer than it took itself. Handed back
|
|
8
|
+
* after every step, the download map's first pass over a two-hour AVI took
|
|
9
|
+
* 14.7-22 s of wall time for about 4 s of its own work, and the start forecast,
|
|
10
|
+
* which needs the whole pass, could not answer meanwhile; held in one stretch
|
|
11
|
+
* it held the thread for that work (Home Assistant 2026-10-12,
|
|
12
|
+
* torrent-tv/meta#178).
|
|
13
|
+
*
|
|
14
|
+
* @param {{ now?: () => number, nextTurn?: () => Promise<void> }} [options]
|
|
15
|
+
* @returns {() => Promise<boolean>} Resolves true when the thread was handed back.
|
|
16
|
+
*/
|
|
17
|
+
export function takeTurns({ now = () => performance.now(), nextTurn = () => new Promise(resolve => setImmediate(resolve)) } = {}) {
|
|
18
|
+
let workingSince = now();
|
|
19
|
+
let othersTook = 0;
|
|
20
|
+
return async () => {
|
|
21
|
+
if (now() - workingSince < othersTook) return false;
|
|
22
|
+
const handedBackAt = now();
|
|
23
|
+
await nextTurn();
|
|
24
|
+
workingSince = now();
|
|
25
|
+
othersTook = workingSince - handedBackAt;
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
}
|