From ca9e1316be598b0d93343c66500af455c1ea2678 Mon Sep 17 00:00:00 2001 From: James Russo Date: Fri, 26 Jun 2026 08:47:53 -0700 Subject: [PATCH] perf(producer): stream binary file responses, async-read HTML (#1735) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(producer): stream binary file responses, async-read HTML Replaces the per-request readFileSync in fileServer's static file handler with a createReadStream pipe (binary) and an async readFile (HTML). Static asset serving no longer blocks the Node event loop. Why --- The pre-fix handler called readFileSync(filePath) on every binary asset. On video-heavy compositions Chrome requests several 32MB video files back-to-back; each readFileSync(32MB) blocked the main event loop long enough to wedge concurrent /health responses and other timers. Scope clarification — this addresses the event-loop block documented at renderOrchestrator.ts:1277-1306 (the video-heavy regression class). It is NOT the fix for today's infinite-duration incident; Miguel is shipping that upstream as a plan()-time duration guard. The two are complementary: - Miguel's guard kills the impossible-work input shape before chunk planning so the producer doesn't try to enumerate 300B frames. - This streaming fix removes the next-largest known main-thread block (large binary I/O during video-heavy renders), so future wedge classes don't kill otherwise-healthy probes either. The companion worker_thread /health PR + the heygen-com/app probe-timeout bump round out the defense-in-depth: even if some future code path introduces another main-thread stall, the probe lives off-thread and the budget is 30s anyway. What changed ------------ fileServer.ts: switched both file branches off the sync I/O path. - Binary (the hot path for video-heavy renders): readFileSync(filePath) -> createReadStream + Readable.toWeb -> Response stream body. Content-Length is set via statSync so Chrome's range-aware media stack sees the size up front. The handler is now async because the HTML branch awaits. - HTML (small files; injected with pre/head/body scripts): readFileSync(filePath, "utf-8") -> readFile(filePath, "utf-8"). The injection is still sync — pure string ops — only the disk read moved off-thread. Index HTMLs are tiny (~200KB max for AI-generated compositions) but a ms of stall per render-start adds up across a fleet. Test ---- fileServer.test.ts: added a streaming regression that pins three properties on a 5MB synthetic binary asset (chunk-boundary spanning): 1. Correctness — served bytes match the file across multiple createReadStream chunks (default 64KB highWaterMark). 2. Content-Length header is set from statSync. 3. Four parallel fetches all return identical content; the streaming path doesn't serialize them. All 31 fileServer tests pass locally (bun test). TODO: link Miguel's upstream plan() duration guard PR once known. — Jerrai Co-Authored-By: Claude Opus 4.7 * fix(producer): implement Accept-Ranges + 206 Partial Content for fileServer Delivers the range-request semantics the original PR body promised but the diff did not implement. Without range support, Chrome's