{"record":{"id":"8b6843b3b449546e","repo":"denoland/deno","slug":"stream-reader-has-shut-down","errorCode":null,"errorMessage":"stream reader has shut down","messagePattern":"stream reader has shut down","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ext/http/reader_stream.rs","lineNumber":58,"sourceCode":"      inner: ReaderStream::new(reader),\n      done: done.clone(),\n    };\n    (this, ShutdownHandle(done))\n  }\n}\n\nimpl<R: AsyncRead> Stream for ExternallyAbortableReaderStream<R> {\n  type Item = std::io::Result<Bytes>;\n\n  fn poll_next(\n    self: Pin<&mut Self>,\n    cx: &mut Context<'_>,\n  ) -> Poll<Option<Self::Item>> {\n    let this = self.project();\n    let val = std::task::ready!(this.inner.poll_next(cx));\n    match val {\n      None if this.done.load(Ordering::SeqCst) => Poll::Ready(None),\n      None => Poll::Ready(Some(Err(std::io::Error::new(\n        std::io::ErrorKind::UnexpectedEof,\n        \"stream reader has shut down\",\n      )))),\n      Some(val) => Poll::Ready(Some(val)),\n    }\n  }\n}\n\n#[cfg(test)]\nmod tests {\n  use bytes::Bytes;\n  use deno_core::futures::StreamExt;\n  use tokio::io::AsyncWriteExt;\n\n  use super::*;\n\n  #[tokio::test]\n  async fn success() {","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/http/reader_stream.rs#L40-L76","documentation":"ExternallyAbortableReaderStream (ext/http/reader_stream.rs) adapts a tokio AsyncRead into the hyper response body — used for auto-compressed bodies, where the handler writes into a duplex pipe and this stream feeds hyper. A clean end requires ShutdownHandle::shutdown(), set by op_http_shutdown when the response finishes; if the reader hits EOF without that flag, the stream emits io::ErrorKind::UnexpectedEof 'stream reader has shut down' so the truncated body aborts the transfer instead of appearing successful.","triggerScenarios":"A Deno.serve handler writes part of a compressible (gzip/brotli auto-negotiated) body and then abandons it: the handler throws mid-write, the response body writer is dropped without a proper close/abort, or the shutdown path is never invoked because the handler errored while the compressed body was partially streamed.","commonSituations":"Streaming generators that throw after emitting some chunks; handlers returning early after partial writes; clients observing dropped connections with truncated content-length'd bodies behind proxies.","solutions":["Wrap the streaming body generator in try/finally and finish or explicitly close/abort the writer on error","Do not write any body bytes before you are certain the response can complete","Handle the upstream error before any bytes reach the compressed writer","On the client side, treat UnexpectedEof on a content-length'd response as partial data and retry idempotent requests"],"exampleFix":"// before\nnew Response(\n  (async function* () {\n    yield part1;\n    throw new Error('boom'); // body abandoned mid-stream\n  })(),\n);\n\n// after\nnew Response(\n  (async function* () {\n    try {\n      yield part1;\n      throw new Error('boom');\n    } catch (e) {\n      console.error('body failed', e);\n      yield encoder.encode('partial content'); // or close cleanly\n    }\n  })(),\n);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// server side: never abandon a partially written body\nconst body = new ReadableStream({\n  async start(controller) {\n    try {\n      for await (const chunk of source()) controller.enqueue(chunk);\n      controller.close();\n    } catch (e) {\n      controller.error(e); // client sees a clean stream error, not a truncated body\n    }\n  },\n});\n\n// client side: treat EOF-without-end as partial content\nconst res = await fetch(url);\nconst text = await res.text().catch((e) => {\n  if (/unexpected EOF|stream reader has shut down/i.test(String(e))) {\n    throw new Error('Partial response body; safe to retry');\n  }\n  throw e;\n});","preventionTips":["Finish or explicitly close/abort every response body writer before the handler returns","Validate inputs before writing the first body byte so errors happen header-only","For generators that may fail mid-stream, error the controller instead of returning","Retry idempotent requests when a content-length'd body ends with UnexpectedEof"],"tags":["http","server","stream","unexpected-eof","compression","deno"],"backgroundTag":"stream-unexpected-eof","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}