{"record":{"id":"e0fbd5013257ca54","repo":"cloudflare/pingora","slug":"body-buf-exists-once-a-chunk-was-parsed-out-of-it","errorCode":null,"errorMessage":"body buf exists once a chunk was parsed out of it","messagePattern":"body buf exists once a chunk was parsed out of it","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pingora-core/src/protocols/http/v1/body.rs","lineNumber":639,"sourceCode":"                        /* expecting_from_io < existing_buf_end */\n                        self.body_state =\n                            self.body_state.multi_chunk(payload_size, expecting_from_io);\n\n                        return Ok(Some(BufRef::new(0, payload_size)));\n                    }\n                    let (buf_res, last_chunk_size_end) =\n                        self.parse_chunked_buf(existing_buf_start, existing_buf_end)?;\n                    if buf_res.is_some() {\n                        if let Some(idx) = last_chunk_size_end {\n                            // just read the last 0 + CRLF, but not final end CRLF\n                            // Split the rest of the buffer to the start in O(1) so trailers and\n                            // any following pipelined request remain in the same allocation.\n                            // `do_read_chunked_body_final` must not reset this buffer, see the\n                            // `existing_buf_end` handling there.\n                            let mut body_buf = self\n                                .body_buf\n                                .take()\n                                .expect(\"body buf exists once a chunk was parsed out of it\");\n                            trace!(\n                                \"last chunk size end buf {:?}\",\n                                body_buf[..existing_buf_end].escape_ascii(),\n                            );\n                            body_buf.truncate(existing_buf_end);\n                            self.body_buf = Some(body_buf.split_off(idx));\n                        }\n                    }\n                    Ok(buf_res)\n                }\n            }\n            _ => panic!(\"wrong body state: {:?}\", self.body_state),\n        }\n    }\n\n    // Returns: BufRef of next body chunk,\n    // terminating chunk-size index end if read completely (0 + CRLF).\n    // Note input indices are absolute (to body_buf).","sourceCodeStart":621,"sourceCodeEnd":657,"githubUrl":"https://github.com/cloudflare/pingora/blob/0046038bd402bc82912da862dadf9a479f31e9f1/pingora-core/src/protocols/http/v1/body.rs#L621-L657","documentation":"While finishing a chunked body, the parser detects the terminal 0-size chunk and splits the remaining buffer (trailers/pipelined data) in O(1); it .take()s body_buf and .expect()s it to exist ('body buf exists once a chunk was parsed out of it', body.rs:639). Reaching that branch with no body_buf violates the parser invariant that parsing chunks out of the buffer implies the buffer exists, so this panic indicates an internal bug or desynchronized state, not a caller error.","triggerScenarios":"A chunked response ending with the final chunk (+ optional trailers) processed through a parser/buffer state where body_buf was already consumed or reset — typically a pingora bug on specific trailer/pipelining sequences, or custom code resetting the session body buffer between reads.","commonSituations":"Responses with trailers or pipelined follow-up requests after the last chunk; fuzzed chunked streams; mixing cache body readers that consume the buffer with the H1 parser's assumptions.","solutions":["Upgrade pingora-core to the latest release and retest the offending traffic","Capture and minimize the failing response (final chunk + trailers) and report it upstream to pingora","Review any custom body-buffer handling (cache read/write filters) that could take or reset the H1 session buffer mid-parse"],"exampleFix":"# before\npingora-core = \"0.7\"\n\n# after\ncargo update -p pingora-core  # includes H1 chunked/trailer fixes","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Wrap per-connection proxying so parser panics degrade to 502, not a crash\nlet outcome = tokio::spawn(handle_connection(stream)).await;\nmatch outcome {\n    Ok(r) => r,\n    Err(e) if e.is_panic() => {\n        log::error!(\"H1 chunked/trailer parse panicked: {e:?}\");\n        respond_502_and_close()\n    }\n    Err(e) => Err(e.into()),\n}","preventionTips":["Exercise trailer and pipelined-after-last-chunk traffic in tests; these are the rare paths here","Avoid custom body-buffer manipulation (cache readers, filters) that could consume the H1 buffer mid-parse","Report reproducible cases upstream with the raw response bytes and stay current on pingora-core patches"],"tags":["rust","http1","body-parser","chunked-encoding","trailers","pingora","invariant","panic"],"backgroundTag":"http-body-parser-panic","analyzedSha":"0046038bd402bc82912da862dadf9a479f31e9f1","analyzedAt":"2026-08-16T21:33:22.341Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}