Skip to content

http1connection,websocket: Avoid event loop starvation from small chunks - #3773

Merged
bdarnell merged 3 commits into
tornadoweb:masterfrom
bdarnell:claude/beautiful-fermi-dmc8wl
Oct 7, 2026
Merged

bdarnell merged 3 commits into
tornadoweb:masterfrom
bdarnell:claude/beautiful-fermi-dmc8wl

Conversation

@bdarnell

@bdarnell bdarnell commented Oct 7, 2026

Copy link
Copy Markdown
Member

Connections that send many tiny chunks of data in either Transfer-Encoding: chunked or websockets could monopolize the IOLoop for an uninterrupted time. With this PR, we yield after every 256KB so that other requests can be handled.

Websockets are now limited to one outstanding "pong" response at a time to mitigate ping floods (this is suggested in the RFC).

Finally, small chunks in Transfer-Encoding: chunked may be combined in batches before giving them to the application to reduce overhead.

claude added 3 commits October 7, 2026 17:28
Reads of body data that has already arrived complete without returning
control to the IOLoop, so a client sending a chunked body made of many
tiny chunks (which cost ~6us each to parse) could block the event loop
for the entire body (minutes, with the default max_body_size).

- Yield to the IOLoop periodically while reading any body (every 256KB,
  with each chunk of a chunked body counting as 1KB).
- Coalesce consecutive small chunks before calling data_received. Pending
  data is flushed whenever we would wait for the network, so streaming
  latency is unaffected.
- Accumulate non-streaming bodies in a bytearray instead of a list of
  bytes objects, which used ~120 bytes of memory per 1-byte chunk.
- Raise HTTPInputError instead of using assert for a chunk that is not
  followed by CRLF.
- Document the baseline for denial-of-service reports in SECURITY.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XKZM6XMVNYGibRyDU6QZW
Measures CPU per byte, event loop stalls, and memory for adversarial
inputs (tiny chunks, pipelined requests, tiny websocket messages and
fragments, ping floods) to serve as the baseline described in
SECURITY.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XKZM6XMVNYGibRyDU6QZW
Reads of frames that have already arrived complete without returning
control to the IOLoop, so a peer sending many small frames could block
the event loop indefinitely. Yield every 256KB, with each frame counting
as 1KB.

A peer that sends pings without reading the pongs could make the
server's write buffer (and the per-write Futures, ~500 bytes each) grow
without limit. As permitted by RFC 6455 section 5.5.3, keep at most one
pong outstanding and answer only the most recent ping received while it
is pending. Pongs are also no longer sent after a close frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XKZM6XMVNYGibRyDU6QZW
@bdarnell
bdarnell merged commit 61beacf into tornadoweb:master Oct 7, 2026
28 of 33 checks passed
@bdarnell
bdarnell deleted the claude/beautiful-fermi-dmc8wl branch October 7, 2026 19:25
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