Skip to content

feat: add prepared write - #574

Open
kvizyx wants to merge 6 commits into
coder:masterfrom
kvizyx:kvizyx/prepared-message
Open

kvizyx wants to merge 6 commits into
coder:masterfrom
kvizyx:kvizyx/prepared-message

Conversation

@kvizyx

@kvizyx kvizyx commented Oct 2, 2026

Copy link
Copy Markdown

Closes #546

Adds PreparedMessage and Conn.WritePrepared for writing the same message to many connections, such as when broadcasting. On connections that compress without context takeover, the message is compressed once and the result is shared instead of being compressed per connection.

Other connections use the regular write path: with context takeover each connection compresses against its own sliding window, so the result cannot be shared, and without compression there is nothing expensive to share. Only the compressed payload is cached so it works for both server and client connections, and clients still mask every write with a new key. On Wasm, WritePrepared is equivalent to Write.

Without context takeover, Write and WritePrepared now also send a message uncompressed if compression does not make it smaller, such as already compressed data, as RFC 7692 allows. With context takeover the message stays compressed to keep the peer's sliding window in sync (picked up this approach from uWebSockets).

Also adds a broadcast example in internal/examples/broadcast.

Benchmarks

BenchmarkBroadcast writes a 1 KiB message to 16 connections per op:

Benchmark Write WritePrepared Delta
Server, no context takeover 53.5 µs, 3.2 KiB, 64 allocs 14.8 µs, 5.0 KiB, 70 allocs -72.29%
Client, no context takeover 68.4 µs, 3.4 KiB, 64 allocs 28.6 µs, 5.2 KiB, 70 allocs -58.25%
Server, context takeover 46.4 µs, 2.4 KiB, 64 allocs 45.0 µs, 3.5 KiB, 66 allocs -2.94%
Client, context takeover 58.3 µs, 2.4 KiB, 64 allocs 59.4 µs, 3.5 KiB, 66 allocs ~
Server, compression disabled 11.8 µs, 2.4 KiB, 64 allocs 12.4 µs, 3.5 KiB, 66 allocs +5.21%
Client, compression disabled 26.7 µs, 2.4 KiB, 64 allocs 26.6 µs, 3.5 KiB, 66 allocs ~

The extra allocations happen once per broadcast, not per connection: 2 for NewPreparedMessage (the struct and its copy of the message) + 4 for compressing the message once without context takeover.


AI: Part of this changes were written with AI. I reviewed all of the code :)

kvizyx added 4 commits October 2, 2026 03:55
Broadcasting the same message with compression enabled compressed it once
per connection, which dominates CPU cost when fanning out to many peers.
PreparedMessage compresses the payload once and shares it across
connections that compress without context takeover while other connections use
the regular write path.
@kvizyx
kvizyx force-pushed the kvizyx/prepared-message branch from 9d1be01 to 385007a Compare October 2, 2026 21:17

This branch has not been deployed

No deployments
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.

Are there any plans to implement prepared writes?

1 participant