an encode ceiling that fits real video

JOB_TIMEOUT_MS was 120 seconds, which is a budget rather than a ceiling: it fits a short clip and
nothing else. Video encoding is measured against the length of the video — a 30-second hero at two
renditions takes about 40 seconds — so a two-minute upload could not finish and failed with "Encoding
took longer than 120s and was stopped". The file was fine, the encoder was working, and it was killed
for being ordinary.

Thirty minutes now. It is still a ceiling and still does the job it was written for: a crafted file
cannot hold a worker for the life of the process. Nothing legitimate this service is asked for
approaches half an hour, and a runaway is still stopped.

The default has to fit the worst LEGITIMATE case, because guessing low costs a rejected upload and
guessing high costs a runaway running a little longer before being killed anyway. A machine that only
converts photographs can tighten it with AV_CLUB_JOB_TIMEOUT_MS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-30 23:10:23 +01:00
co-authored by Claude Opus 5
parent ec6e09014f
commit 62972c1146
+27 -2
View File
@@ -80,8 +80,33 @@ export const MAX_UPLOAD_BYTES = num("AV_CLUB_MAX_UPLOAD_BYTES", 512 * 1024 * 102
*/ */
export const CONVERT_MEMORY_MB = num("AV_CLUB_CONVERT_MEMORY_MB", 256, 32); export const CONVERT_MEMORY_MB = num("AV_CLUB_CONVERT_MEMORY_MB", 256, 32);
/** A crafted file can make an encoder run effectively forever. The process is killed at this point. */ /**
export const JOB_TIMEOUT_MS = num("AV_CLUB_JOB_TIMEOUT_MS", 120_000, 1_000); * How long one encode may run before it is killed.
*
* ── What it is for ──
*
* A crafted file can make an encoder run effectively forever. This is the ceiling that stops one
* request holding a worker for the life of the process. It is a guard against FOREVER, not a budget
* for how long work should take.
*
* ── Why it is not 120 seconds any more ──
*
* It was, and that is a budget rather than a ceiling — it fits a short clip and nothing else. Video
* encoding is measured against the length of the video: a 30-second hero at two renditions took about
* 40 seconds, so a two-minute upload could not finish and failed with "Encoding took longer than 120s
* and was stopped." The file was fine, the encoder was working, and it was killed for being ordinary.
*
* Thirty minutes is still a ceiling. Nothing legitimate this service is asked for approaches it, and a
* runaway is still stopped rather than left running — which is the whole property being defended.
*
* ── Tightening it ──
*
* `AV_CLUB_JOB_TIMEOUT_MS`, per machine. A host that only ever converts photographs can safely set this
* low; one that takes long video should leave it alone. The default has to fit the worst legitimate
* case, because the cost of guessing low is a rejected upload and the cost of guessing high is a
* runaway that runs a while longer before being killed anyway.
*/
export const JOB_TIMEOUT_MS = num("AV_CLUB_JOB_TIMEOUT_MS", 1_800_000, 1_000);
/** /**
* Decompression bombs: a 1 KB PNG can expand to tens of gigabytes. This is sharp's own * Decompression bombs: a 1 KB PNG can expand to tens of gigabytes. This is sharp's own