{"record":{"id":"d445ffac79bf912b","repo":"denoland/deno","slug":"no-h2-capacity","errorCode":null,"errorMessage":"no h2 capacity","messagePattern":"no h2 capacity","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ext/websocket/stream.rs","lineNumber":128,"sourceCode":"              ErrorKind::ConnectionReset,\n              e,\n            )));\n          }\n          None => {\n            // The h2 stream is closed (typically a peer-sent\n            // RST_STREAM). Surface as a write error instead of\n            // panicking on `capacity() == 0` below. See #33953.\n            return Poll::Ready(Err(std::io::Error::new(\n              ErrorKind::ConnectionReset,\n              \"h2 stream closed\",\n            )));\n          }\n        }\n\n        // We'll try to send whatever we have capacity for.\n        let size = std::cmp::min(buf.len(), send.capacity());\n        if size == 0 {\n          return Poll::Ready(Err(std::io::Error::new(\n            ErrorKind::WriteZero,\n            \"no h2 capacity\",\n          )));\n        }\n\n        let buf: Bytes = Bytes::copy_from_slice(&buf[0..size]);\n        let len = buf.len();\n        // TODO(mmastrac): surface the h2 error?\n        let res = send\n          .send_data(buf, false)\n          .map_err(|_| std::io::Error::from(ErrorKind::Other));\n        Poll::Ready(res.map(|_| len))\n      }\n    }\n  }\n\n  fn poll_flush(\n    mut self: Pin<&mut Self>,","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/websocket/stream.rs#L110-L146","documentation":"Companion guard on the same WebSocket-over-HTTP/2 write path: after capacity was successfully reserved, send.capacity() is re-read; if it is 0 the write ends immediately with ErrorKind::WriteZero and this message. It marks an h2 edge where reserved capacity vanished (stream closing concurrently) so the code fails cleanly instead of sending an empty DATA frame.","triggerScenarios":"A WS-over-h2 write racing stream closure: capacity poll succeeded, then RST_STREAM/teardown drained the window before send_data, producing a zero-capacity write. Same triggers as 'h2 stream closed' but hitting the narrower window.","commonSituations":"Teardown races under load (burst of sends as the peer closes); proxies resetting streams mid-write; reconnect storms where old sockets are still being written to.","solutions":["Handle WriteZero exactly like ConnectionReset: close the socket and reconnect.","Serialize writes through a queue that is flushed/aborted on close so no write races teardown.","Check readyState and use a closed flag set in onclose/onerror before each send.","Reduce end-of-life chatter: stop heartbeats/acks as soon as close handshake begins."],"exampleFix":"// before\nconst ok = ws.send(frame); // may throw WriteZero: no h2 capacity during close race\n\n// after\nif (!open) return;            // open flag cleared in onclose/onerror\ntry { ws.send(frame); }\ncatch (e) { open = false; ws.close(); reconnectLater(); }","handlingStrategy":"try-catch","validationCode":"if (!open || ws.readyState !== WebSocket.OPEN) return; // skip send entirely during teardown","typeGuard":"const isCapacityError = (e) => e instanceof Error && (/no h2 capacity/.test(e.message) || String(e.code ?? \"\").includes(\"EPIPE\"));","tryCatchPattern":"try { ws.send(frame); } catch (e) { if (/no h2 capacity/.test(String(e.message))) { open = false; ws.close(); reconnectLater(); } else throw e; }","preventionTips":["Serialize sends through one queue drained by a single writer so teardown aborts cleanly.","Stop heartbeats/acks as soon as the close handshake begins.","Never fire-and-forget writes right before intentional close()."],"tags":["websocket","http2","h2","write-zero","connection-reset"],"backgroundTag":"connection-reset","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}