Files
hyperframes/packages
James RussoandJerrai ca9e1316be perf(producer): stream binary file responses, async-read HTML (#1735)
* 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 <noreply@anthropic.com>

* 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 <video>
element issues full-file GETs on seek; with this commit it can issue
`Range: bytes=...` and get a sliced 206 back, so seek + partial-load
work without re-pulling the whole file.

- Add `parseRangeHeader` (exported for unit tests) covering the three
  RFC 7233 single-range forms: bytes=START-END (closed), bytes=START-
  (open-ended), bytes=-SUFFIX (last N bytes). Multi-range falls back to
  `absent` (full 200) so we never reassemble multipart/byteranges.
- Binary path now returns 206 Partial Content with Content-Range +
  sliced Content-Length on satisfiable ranges, 416 Range Not Satisfiable
  with `Content-Range: bytes (asterisk)/<size>` on unsatisfiable ranges,
  and 200 with `Accept-Ranges: bytes` on full-body GETs so clients know
  ranges are supported.
- Add unit tests for parseRangeHeader (10 cases: 3 forms, clamping,
  unsatisfiable edges, malformed inputs, multi-range fallback).
- Add integration test covering 200 + Accept-Ranges, all 3 range forms
  with byte-correct slices, 416 on out-of-bounds, and multi-range -> 200
  fallback.

Addresses Miga's review finding on #1735.

Co-Authored-By: Jerrai <noreply@anthropic.com>

— Jerrai

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 08:47:53 -07:00
..
2026-06-26 00:51:34 -04:00
2026-06-26 00:51:34 -04:00
2026-06-26 00:51:34 -04:00
2026-06-26 00:51:34 -04:00
2026-06-26 00:51:34 -04:00
2026-06-16 13:16:09 -04:00
2026-06-26 00:51:34 -04:00