mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-05 10:14:30 +00:00
fix(producer): harden capture against timeouts, transient tab deaths, and OOM (#1842)
Four independent capture-infra hardening changes for the P2-5 failure bucket (~15K err / ~7K users): - protocolTimeout auto-scales by device-scaled output area (applied before probe launch, since it's immutable post ppt.launch()). - Single bounded transient retry (MAX_TRANSIENT_CAPTURE_RETRIES=1) on Target closed / Page crashed in the parallel disk-capture path; abort short-circuits before retry. - Narrow OOM classification (Set maximum size exceeded etc., disjoint from transient) → actionable guidance naming output dims. - StreamingEncoder.getExitError() threads FFmpeg's real exit reason into frame-0 encoder-death errors. Render-reliability workstream P2-5. Success measured on PostHog dashboard 1783183. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
180f368af1
commit
c0c3abf0f1
@@ -526,6 +526,55 @@ describe("spawnStreamingEncoder lifecycle and cleanup", () => {
|
||||
expect(result.error).toContain("Encoder error");
|
||||
});
|
||||
|
||||
it("getExitError surfaces the ffmpeg failure reason after a non-zero exit", async () => {
|
||||
const { spawn, calls } = createSpawnSpy();
|
||||
vi.resetModules();
|
||||
vi.doMock("child_process", () => ({ spawn }));
|
||||
|
||||
const { spawnStreamingEncoder } = await import("./streamingEncoder.js");
|
||||
const dir = mkdtempSync(join(tmpdir(), "se-exiterr-"));
|
||||
const encoder = await spawnStreamingEncoder(join(dir, "out.mp4"), baseOptions);
|
||||
|
||||
const proc = calls[0]!.proc;
|
||||
// While running, there is no exit error to report.
|
||||
expect(encoder.getExitError()).toBeUndefined();
|
||||
|
||||
proc.stderr.emit("data", Buffer.from("Unknown encoder 'libx264'\n"));
|
||||
await new Promise<void>((resolve) => {
|
||||
process.nextTick(() => {
|
||||
proc.emit("close", 1);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// After a non-zero exit, the reason is available synchronously — this is
|
||||
// what `ensureFrameWritten` reads to turn "encoder exited before frame 0"
|
||||
// into an actionable message.
|
||||
const exitError = encoder.getExitError();
|
||||
expect(exitError).toContain("FFmpeg exited with code 1");
|
||||
expect(exitError).toContain("Unknown encoder 'libx264'");
|
||||
});
|
||||
|
||||
it("getExitError returns undefined after a clean exit", async () => {
|
||||
const { spawn, calls } = createSpawnSpy();
|
||||
vi.resetModules();
|
||||
vi.doMock("child_process", () => ({ spawn }));
|
||||
|
||||
const { spawnStreamingEncoder } = await import("./streamingEncoder.js");
|
||||
const dir = mkdtempSync(join(tmpdir(), "se-exitok-"));
|
||||
const encoder = await spawnStreamingEncoder(join(dir, "out.mp4"), baseOptions);
|
||||
|
||||
const proc = calls[0]!.proc;
|
||||
await new Promise<void>((resolve) => {
|
||||
process.nextTick(() => {
|
||||
proc.emit("close", 0);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
expect(encoder.getExitError()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns a failure result (does NOT throw) when ffmpeg fails to spawn (ENOENT)", async () => {
|
||||
const { spawn, calls } = createSpawnSpy();
|
||||
vi.resetModules();
|
||||
|
||||
Reference in New Issue
Block a user