Skip to content

CONCEPT Cited by 1 source

AsyncRead contract

Definition

AsyncRead (and its sibling AsyncWrite) are async Rust traits that extend Future to streaming I/O. Where a one-shot Future resolves once and then can't be polled again, an AsyncRead's poll_read method returns Poll::Ready(Ok(n)) every time there's data ready to read. The contract is: keep calling poll_read in a loop until it stops being Ready (i.e. returns Pending).

From Fly.io's 2025-02 proxy post:

"With network programming, data usually arrives in streams, which you want to track and make progress on as you can. So async Rust provides AsyncRead and AsyncWrite traits, which build on Futures, and provide methods like poll_read that return Ready every time there's data ready."

Implicit contract that can't be checked

The crucial and easy-to-miss half of the contract is: every Ready must make forward progress in the underlying state machine. If poll_read returns Ready without advancing — e.g. without actually consuming buffered bytes, without transitioning a TLS-protocol state, without advancing the read cursor — the caller (who is faithfully looping until Pending) spins on it forever.

Fly.io: "Since the idea of AsyncRead is that you keep poll_reading until it stops being Ready, this too is an infinite loop."

This is a contract that is easy to write, impossible for the compiler to check, and catastrophic to violate — all the ingredients for exactly the kind of subtle busy-loop that the 2025-02 rustls TlsStream bug instantiated.

Seen in

  • sources/2025-02-26-flyio-taming-a-voracious-rust-proxy — canonical wiki instance. On TLS orderly shutdown (close_notify Alert) with bytes still buffered in the underlying socket, tokio_rustls::server::TlsStream returned Ready from poll_read in a state its own machine couldn't progress from — caller loop → 100% CPU. The failure was the exact second-footgun Fly.io's async-Rust primer warned about.
Last updated · 200 distilled / 1,178 read