denoland/deno · error · TypeError

Socket is already destroyed - cannot upgrade to WebSocket

Error message

Socket is already destroyed - cannot upgrade to WebSocket

What it means

On the node:http upgrade path of Deno.upgradeWebSocket (when options.socket is a node net.Socket received from a server 'upgrade' event), Deno must write the 101 response and detach the TCP stream from libuv. It refuses if nodeSocket.destroyed is already true, because a destroyed socket cannot carry the WebSocket traffic.

Source

Thrown at ext/http/02_websocket.ts:156

      }
    })();
  } else if (options.socket) {
    // node:http upgrade path: the socket is a node net.Socket from the
    // "upgrade" event. Write the 101 response, take the TCP stream from
    // libuv, and create a WebSocket over it via ext/http.
    const nodeSocket = options.socket;

    // Build the 101 response from r.headerList so the headers stay in
    // sync with the header-list built above (protocol negotiation, etc.).
    let responseHead = "HTTP/1.1 101 Switching Protocols\r\n";
    for (let i = 0; i < r.headerList.length; i++) {
      const { 0: name, 1: value } = r.headerList[i];
      responseHead += `${name}: ${value}\r\n`;
    }
    responseHead += "\r\n";

    if (nodeSocket.destroyed) {
      throw new TypeError(
        "Socket is already destroyed - cannot upgrade to WebSocket",
      );
    }
    const handle = nodeSocket._handle;
    if (!handle) {
      throw new TypeError("Socket has no handle - cannot upgrade");
    }
    if (typeof handle.takeStream !== "function") {
      throw new TypeError(
        "Socket is not a TCP socket - only TCP connections can be upgraded to WebSocket",
      );
    }

    // Extra bytes that were already buffered (e.g., from the upgrade
    // request body that arrived with the headers)
    const extraBytes = options.head || new Uint8Array(0);

    // Defer setup so the caller can attach event handlers (onopen,

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Check socket.destroyed before upgrading and bail out (close/ignore) instead of throwing.
  2. Perform the upgrade synchronously in the 'upgrade' event handler, not after awaiting something.
  3. Remove middleware/error paths that destroy upgrade sockets before the handler runs.
  4. Raise or disable the relevant socket timeouts if long setup is required.

Example fix

// before
server.on("upgrade", (req, socket) => {
  socket.destroy(); // e.g. auth failure path ran first
  const { response } = Deno.upgradeWebSocket(req, { socket, head });
});

// after
server.on("upgrade", async (req, socket, head) => {
  if (socket.destroyed) return; // guard
  const { socket: ws, response } = Deno.upgradeWebSocket(req, { socket, head });
  ws.on("message", (m) => console.log(m));
});
Defensive patterns

Strategy: validation

Validate before calling

server.on("upgrade", (req, socket, head) => {
  if (socket.destroyed) return; // client gone / earlier destroy
  const { socket: ws } = Deno.upgradeWebSocket(req, { socket, head });
});

Type guard

function isLiveNodeSocket(s: unknown): boolean { const sock = s as { destroyed?: boolean; _handle?: unknown } | null; return !!sock && sock.destroyed !== true; }

Try / catch

try { Deno.upgradeWebSocket(req, { socket, head }); } catch (e) { if (e instanceof TypeError && e.message.includes("already destroyed")) { socket.destroy(); return; } throw e; }

Prevention

When it happens

Trigger: Calling Deno.upgradeWebSocket(req, { socket }) inside a server.on('upgrade') handler after the socket timed out, errored, or was explicitly destroyed (e.g. in a prior error branch or by middleware that closes sockets); deferring the upgrade asynchronously until after disconnect; client disconnecting between the upgrade event and the handler body.

Common situations: Express/Node-compat servers where earlier middleware or error handlers call socket.destroy()/res.socket.destroy(); idle-timeout policies (server.headersTimeout/requestTimeout) destroying sockets; race with client cancel during slow async handlers; double handling of the same upgrade event by two listeners.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/e2d57c933da2542a. Report an issue: GitHub.