@torrent-tv/proxy 2.101.37 → 2.101.39
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
|
@@ -219,6 +219,7 @@ export async function handleApiTranscodeSessionsPost(req, reply, { viewerRequest
|
|
|
219
219
|
return reply.code(500).send({ error: `Failed to prepare transcode session: ${message}` });
|
|
220
220
|
} finally {
|
|
221
221
|
reply.raw?.removeListener?.("close", cancelled);
|
|
222
|
+
const handlerReturnAt = performance.now();
|
|
222
223
|
const elapsed = (key) => Number.isFinite(timing[key]) ? timing[key].toFixed(1) : "n/a";
|
|
223
224
|
logger.info(
|
|
224
225
|
`transcode-session timing outcome=${cancellation.signal.aborted ? "cancelled" : "handler-return"} ` +
|
|
@@ -226,7 +227,8 @@ export async function handleApiTranscodeSessionsPost(req, reply, { viewerRequest
|
|
|
226
227
|
`consumer=${consumerId || "unknown"} file=${fileIndex} openCalls=${timing.openCalls} ` +
|
|
227
228
|
`openMs=${timing.openMs.toFixed(1)} planWaitInclusiveMs=${elapsed("planWaitInclusiveMs")} ` +
|
|
228
229
|
`masterMs=${elapsed("masterMs")} offerMs=${elapsed("offerMs")} tracksMs=${elapsed("tracksMs")} ` +
|
|
229
|
-
`
|
|
230
|
+
`handlerReturnAtMs=${handlerReturnAt.toFixed(3)} ` +
|
|
231
|
+
`handlerMs=${(handlerReturnAt - timing.startedAt).toFixed(1)}`
|
|
230
232
|
);
|
|
231
233
|
}
|
|
232
234
|
}
|
package/server.js
CHANGED
|
@@ -153,6 +153,15 @@ export async function startProxyServer({
|
|
|
153
153
|
// receives torrent source payloads that may be arbitrarily large.
|
|
154
154
|
bodyLimit: 256 * 1024 * 1024 // 256 MB
|
|
155
155
|
});
|
|
156
|
+
const forwardedSessionResponseStarts = new WeakMap();
|
|
157
|
+
const logForwardedSessionResponseStage = (stage, req) => {
|
|
158
|
+
const startedAt = forwardedSessionResponseStarts.get(req);
|
|
159
|
+
const requestId = req.headers["x-ttv-forwarded-request-id"];
|
|
160
|
+
if (startedAt === undefined || typeof requestId !== "string") return;
|
|
161
|
+
const at = performance.now();
|
|
162
|
+
logger.info(`[forecast-http] stage=${stage} requestId=${requestId} atMs=${at.toFixed(3)} ` +
|
|
163
|
+
`elapsedMs=${(at - startedAt).toFixed(3)}`);
|
|
164
|
+
};
|
|
156
165
|
|
|
157
166
|
await app.register(fastifyHelmet, {
|
|
158
167
|
// Proxy serves media to a different origin (registry UI), so CORP must allow cross-origin usage.
|
|
@@ -173,20 +182,32 @@ export async function startProxyServer({
|
|
|
173
182
|
});
|
|
174
183
|
app.addHook("onRequest", async (req, reply) => {
|
|
175
184
|
const requestId = req.headers["x-ttv-forwarded-request-id"];
|
|
176
|
-
if (typeof requestId !== "string") return;
|
|
185
|
+
if (typeof requestId !== "string" || req.method !== "POST" || req.url !== "/api/transcode-sessions") return;
|
|
177
186
|
const startedAt = performance.now();
|
|
187
|
+
forwardedSessionResponseStarts.set(req, startedAt);
|
|
188
|
+
logForwardedSessionResponseStage("onRequest", req);
|
|
178
189
|
reply.raw.once("finish", () => {
|
|
190
|
+
logForwardedSessionResponseStage("finish", req);
|
|
179
191
|
logger.info(`[http-response] requestId=${requestId} method=${req.method} url=${req.url} ` +
|
|
180
192
|
`responseMs=${(performance.now() - startedAt).toFixed(1)} status=${reply.statusCode} ` +
|
|
181
193
|
`contentLength=${reply.raw.getHeader("content-length") ?? "unknown"}`);
|
|
182
194
|
});
|
|
183
195
|
reply.raw.once("close", () => {
|
|
184
196
|
if (!reply.raw.writableFinished) {
|
|
197
|
+
logForwardedSessionResponseStage("close", req);
|
|
185
198
|
logger.warn(`[http-response] requestId=${requestId} method=${req.method} url=${req.url} ` +
|
|
186
199
|
`closedMs=${(performance.now() - startedAt).toFixed(1)} status=${reply.statusCode}`);
|
|
187
200
|
}
|
|
188
201
|
});
|
|
189
202
|
});
|
|
203
|
+
app.addHook("preSerialization", async (req, _reply, payload) => {
|
|
204
|
+
logForwardedSessionResponseStage("preSerialization", req);
|
|
205
|
+
return payload;
|
|
206
|
+
});
|
|
207
|
+
app.addHook("onSend", async (req, _reply, payload) => {
|
|
208
|
+
logForwardedSessionResponseStage("onSend", req);
|
|
209
|
+
return payload;
|
|
210
|
+
});
|
|
190
211
|
|
|
191
212
|
const sourceRegistry = createSourceRegistry(200);
|
|
192
213
|
// The torrent runs on its own thread. Profiling a live seek (2026-08-02)
|
|
@@ -687,8 +708,13 @@ export async function startProxyServer({
|
|
|
687
708
|
const waiting = new Set();
|
|
688
709
|
const turn = takeTurns();
|
|
689
710
|
// ONLY THE SEGMENTS WHOSE RANGES ARE NOT KNOWN are worked out, in the
|
|
690
|
-
// order the swarm is to fetch them.
|
|
711
|
+
// order the swarm is to fetch them. Range resolution can use either a
|
|
712
|
+
// direct source index or the fallback reads below; yield between zones
|
|
713
|
+
// in both cases so a large map cannot hold the main thread across its
|
|
714
|
+
// full projection.
|
|
691
715
|
for (const zone of projection.unresolved()) {
|
|
716
|
+
if (!map.isCurrent()) return [];
|
|
717
|
+
await turn();
|
|
692
718
|
if (!map.isCurrent()) return [];
|
|
693
719
|
const reader = readers.get(zone.outputKey);
|
|
694
720
|
if (!reader) continue;
|
|
@@ -743,14 +769,7 @@ export async function startProxyServer({
|
|
|
743
769
|
waiting.add(zone.outputKey);
|
|
744
770
|
continue;
|
|
745
771
|
}
|
|
746
|
-
// The pass shares the thread in equal turns (`takeTurns`): in one
|
|
747
|
-
// stretch the first pass over a film's 4000 zones held the main thread
|
|
748
|
-
// for all of its work, and handed back after every zone it took
|
|
749
|
-
// 14.7-22 s while the start forecast waited for it. What was worked
|
|
750
|
-
// out is kept, so a pass a newer map ends is not lost
|
|
751
|
-
// (torrent-tv/meta#178).
|
|
752
772
|
if (value?.kind === "result") segmentSourceRanges.set(rangesKey, value.ranges);
|
|
753
|
-
if (await turn() && !map.isCurrent()) return [];
|
|
754
773
|
if (value?.kind === "result") {
|
|
755
774
|
convert(value.ranges);
|
|
756
775
|
continue;
|
|
@@ -1182,6 +1182,13 @@ export function createDataChannelHandler({
|
|
|
1182
1182
|
// route to return a response — e.g. long-polling until an HLS segment is
|
|
1183
1183
|
// finalized by ffmpeg) vs. the body transfer over the data channel.
|
|
1184
1184
|
const fetchStartedAt = Date.now();
|
|
1185
|
+
const traceSessionResponse = method === "POST" && path === "/api/transcode-sessions" &&
|
|
1186
|
+
typeof requestId === "string";
|
|
1187
|
+
const fetchStartedAtMonotonic = traceSessionResponse ? performance.now() : null;
|
|
1188
|
+
if (traceSessionResponse) {
|
|
1189
|
+
log(`[forecast-http] stage=local-fetch-start requestId=${requestId} ` +
|
|
1190
|
+
`atMs=${fetchStartedAtMonotonic.toFixed(3)}`);
|
|
1191
|
+
}
|
|
1185
1192
|
try {
|
|
1186
1193
|
response = await fetch(targetUrl, {
|
|
1187
1194
|
method,
|
|
@@ -1197,6 +1204,12 @@ export function createDataChannelHandler({
|
|
|
1197
1204
|
send(channel, { type: "response-error", requestId, error: fetchError?.message ?? String(fetchError) });
|
|
1198
1205
|
return;
|
|
1199
1206
|
}
|
|
1207
|
+
if (traceSessionResponse) {
|
|
1208
|
+
const headersAt = performance.now();
|
|
1209
|
+
log(`[forecast-http] stage=local-fetch-headers requestId=${requestId} ` +
|
|
1210
|
+
`atMs=${headersAt.toFixed(3)} elapsedMs=${(headersAt - fetchStartedAtMonotonic).toFixed(3)} ` +
|
|
1211
|
+
`status=${response.status}`);
|
|
1212
|
+
}
|
|
1200
1213
|
|
|
1201
1214
|
if (response.status !== 200 && response.status !== 206) {
|
|
1202
1215
|
log(`[dc] ${method} ${path}${queryInfo} → ${response.status}`);
|