denoland/deno · error · NodeError
ERR_SOCKET_CLOSED
ERR_SOCKET_CLOSED
Error message
Socket is closed
What it means
Thrown by the polyfilled OverrideSocket (the Duplex wrapping a Deno.Conn for tunnel/vsock connections) when flushing a chunk. #writeAll() loops on conn.write() because a single write may be partial (vsock send buffers are 64 KiB); a return of 0 bytes means the transport is closed, so the chunk can never be delivered and ERR_SOCKET_CLOSED is raised into the write callback.
Source
Thrown at ext/node/polyfills/internal/http/address_override.js:225
_read(_size) {
if (this._readResume) {
const resume = this._readResume;
this._readResume = null;
resume();
}
}
// Deno.Conn.write() is a single syscall-like write: it may write fewer
// bytes than provided (e.g. when the transport send buffer is full --
// vsock buffers are 64 KiB). Loop until the whole chunk is flushed.
async #writeAll(bytes) {
let nwritten = await this.#conn.write(bytes);
while (nwritten < bytes.length) {
const n = await this.#conn.write(
TypedArrayPrototypeSubarray(bytes, nwritten, bytes.length),
);
if (n === 0) {
throw new ERR_SOCKET_CLOSED();
}
nwritten += n;
}
}
_write(chunk, encoding, callback) {
const bytes = typeof chunk === "string"
? Buffer.from(chunk, encoding)
: chunk;
// Any outgoing byte resets the idle timer too, matching net.Socket.
this.#armTimer();
PromisePrototypeThen(this.#writeAll(bytes), () => callback(), callback);
}
_final(callback) {
try {
// Allow the other side to finish reading while we finish writing.
if (this.#conn.closeWrite) {View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Treat ERR_SOCKET_CLOSED as terminal for that socket: stop writing and destroy it
- Hook 'close'/'error' on the socket (and server 'clientError') and short-circuit further writes for that connection
- Check socket.destroyed / socket.writable before each hot-path write
- If writes legitimately race disconnects, pass a write callback and swallow this specific error after cleanup
Example fix
// before
socket.on("data", (chunk) => peer.write(chunk)); // ERR_SOCKET_CLOSED after peer leaves
// after
socket.on("data", (chunk) => {
if (peer.destroyed) return socket.destroy();
peer.write(chunk, (err) => { if (err) peer.destroy(); });
});
peer.on("close", () => socket.destroy()); Defensive patterns
Strategy: validation
Validate before calling
function isSocketWritable(sock) {
return !sock.destroyed && !sock.closed && sock.writable !== false;
}
if (isSocketWritable(sock)) sock.write(chunk); Try / catch
sock.write(chunk, (err) => {
if (err && err.code === "ERR_SOCKET_CLOSED") {
sock.destroy(); // peer gone: stop writing this connection
}
}); Prevention
- Destroy the paired socket as soon as one side emits 'close' or 'error'
- Guard hot-path writes with socket.writable/destroyed checks
- Register 'error' handlers on every socket so closes never escalate to crashes
When it happens
Trigger: res.write()/res.end() on a tunnel/vsock connection after the peer disconnected; a slow producer whose writes continue after the remote side reset; large responses being flushed in a loop when the peer closes mid-buffer.
Common situations: Clients aborting long downloads; proxies/tunnels forwarding after upstream closed; vsock guests shutting down while the host keeps writing; keep-alive writes hitting a dead connection.
Related errors
- ERR_HTTP_SOCKET_ASSIGNED
- unexpected EOF while reading HTTP/1 body
- Either `node` or `range` must be provided when reporting an
- fetch failed
- Already upgraded
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/26d64d4d50172e33.
Report an issue: GitHub.