Skip to content

fix: Brotli stream corruption via concurrent writes to compressor#16

Merged
rschmukler merged 1 commit intomainfrom
ck/virtual-threads
Feb 27, 2026
Merged

fix: Brotli stream corruption via concurrent writes to compressor#16
rschmukler merged 1 commit intomainfrom
ck/virtual-threads

Conversation

@Camsbury
Copy link
Contributor

  • Introduced a per-tab “single writer” actor (a dedicated virtual thread) that owns:
    • the http-kit AsyncChannel
    • the tab’s streaming Brotli state (br-out + br-stream, when compression is enabled)
  • Changed send-sse! from “compress + http/send! right now” to “enqueue message and return immediately”:
    • Messages go into a LinkedBlockingQueue
    • The actor thread drains the queue and performs the actual compression + network writes sequentially
  • Ensured the SSE response headers are sent exactly once:
    • The actor’s first send writes the {:headers ... :body ...} map (and includes Content-Encoding: br when applicable)
    • Subsequent sends are raw chunks on the same connection
  • Made teardown safer:
    • unregister-sse-channel! now signals the actor to close via a sentinel, so the actor closes the Brotli stream/channel on its own thread (avoiding “close while writing” races)
    • Re-registering a tab now stops any previous writer first (reconnect safety)
  • Updated/added tests to match and validate the new behavior:
    • Tests now account for the async actor (latches/timeouts)
    • Added coverage that send-sse! doesn’t block on Brotli compression and that Brotli compression is never invoked concurrently for a tab.

- **Introduced a per-tab “single writer” actor** (a dedicated virtual thread) that *owns*:
  - the http-kit `AsyncChannel`
  - the tab’s streaming Brotli state (`br-out` + `br-stream`, when compression is enabled)
- Changed `send-sse!` from “compress + `http/send!` right now” to **“enqueue message and return immediately”**:
  - Messages go into a `LinkedBlockingQueue`
  - The actor thread drains the queue and performs the actual compression + network writes sequentially
- Ensured the SSE response headers are sent **exactly once**:
  - The actor’s *first* send writes the `{:headers ... :body ...}` map (and includes `Content-Encoding: br` when applicable)
  - Subsequent sends are raw chunks on the same connection
- Made teardown safer:
  - `unregister-sse-channel!` now **signals the actor to close via a sentinel**, so the actor closes the Brotli stream/channel on its own thread (avoiding “close while writing” races)
  - Re-registering a tab now stops any previous writer first (reconnect safety)
- Updated/added tests to match and validate the new behavior:
  - Tests now account for the async actor (latches/timeouts)
  - Added coverage that `send-sse!` **doesn’t block on Brotli compression** and that Brotli compression is **never invoked concurrently** for a tab.
@rschmukler rschmukler merged commit 085c802 into main Feb 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants