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

  1. Treat ERR_SOCKET_CLOSED as terminal for that socket: stop writing and destroy it
  2. Hook 'close'/'error' on the socket (and server 'clientError') and short-circuit further writes for that connection
  3. Check socket.destroyed / socket.writable before each hot-path write
  4. 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

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


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/26d64d4d50172e33. Report an issue: GitHub.