cloudflare/pingora · error

must have read body buf

Error message

must have read body buf

What it means

finish_body_buf() is an internal helper of pingora's HTTP/1.1 body reader (protocols/http/v1/body.rs:369): when a body read completes it truncates the shared read buffer and splits off overread bytes, assuming self.body_buf was allocated during reading. The .expect()('must have read body buf') fires when the reader's state machine reaches the finish step with no buffer allocated — an internal invariant violation, not a documented user error.

Source

Thrown at pingora-core/src/protocols/http/v1/body.rs:369

            Some(buf) => buf.extend_from_slice(bytes),
            None => {
                let mut buf = BytesMut::with_capacity(bytes.len());
                buf.extend_from_slice(bytes);
                self.body_buf_overread = Some(buf);
            }
        }
    }

    pub fn body_done(&self) -> bool {
        matches!(self.body_state, PS::Complete(_) | PS::Done(_))
    }

    pub fn body_empty(&self) -> bool {
        self.body_state == PS::Complete(0)
    }

    fn finish_body_buf(&mut self, end_of_body: usize, total_read: usize) {
        let body_buf_mut = self.body_buf.as_mut().expect("must have read body buf");
        // remove unused buffer
        body_buf_mut.truncate(total_read);
        let overread_bytes = body_buf_mut.split_off(end_of_body);
        self.body_buf_overread = (!overread_bytes.is_empty()).then_some(overread_bytes);
    }

    pub async fn read_body<S>(&mut self, stream: &mut S) -> Result<Option<BufRef>>
    where
        S: AsyncRead + Unpin + Send,
    {
        match self.body_state {
            PS::Complete(_) => Ok(None),
            PS::Done(_) => Ok(None),
            PS::Partial(_, _) => self.do_read_body(stream).await,
            PS::Chunked(..) => self.do_read_chunked_body(stream).await,
            PS::ChunkedFinal(..) => self.do_read_chunked_body_final(stream).await,
            PS::UntilClose(_) => self.do_read_body_until_closed(stream).await,
            PS::ToStart => panic!("need to init BodyReader first"),

View on GitHub (pinned to 0046038bd4)

Solutions

  1. Upgrade pingora-core to the latest patch release; invariant panics in the H1 body reader are bug candidates that get fixed
  2. If it reproduces, capture the exact request/response bytes (pcap or a logging proxy) and open an issue with pingora including the trace
  3. Audit any custom request/response body filters for reentrant or out-of-order reads of the session body

Example fix

# before: pinned to an older pingora-core with the parser defect
pingora-core = "=0.7.0"

# after: pick up the fix
cargo update -p pingora-core
Defensive patterns

Strategy: try-catch

Try / catch

// Contain H1 body parser panics to a single connection task
let resp = tokio::spawn(async move {
    std::panic::catch_unwind(std::panic::AssertUnwindSafe(async {
        session.read_body().await // any H1 body read path
    }))
    .await
    .unwrap_or_else(|panic| {
        log::error!("H1 body parser panicked: {panic:?}");
        Err(internal_error_502())
    })
});

Prevention

When it happens

Trigger: Any code path that drives Http1Body to body completion without a prior buffered read allocating body_buf — in practice triggered by a pingora bug or by custom filters that reenter/manipulate the downstream or upstream session body state unexpectedly. Surfaces as a panic (502/reset) while proxying an HTTP/1.1 response.

Common situations: Upgrading between pingora versions with parser refactors; unusual HTTP/1.1 traffic (zero-length bodies, early end-of-stream combinations) hitting an untested state; downstream code misusing session body readers concurrently.

Related errors


AI-assisted analysis of cloudflare/pingora@0046038bd4 (2026-08-16). Data as JSON: /api/errors/a8720bace71a4a1d. Report an issue: GitHub.