denoland/deno · error · Error

ERR_HTTP2_SOCKET_BOUND

ERR_HTTP2_SOCKET_BOUND

Error message

The socket is already bound to an Http2Session

What it means

An HTTP/2 session attaches itself to exactly one socket: the Http2Session constructor stores itself on the socket via kBoundSession and throws ERR_HTTP2_SOCKET_BOUND if a session is already bound there. Creating a second session over a socket that already carries one is a programming error, not a recoverable race.

Source

Thrown at ext/node/polyfills/http2.ts:3993

// immediately following the 'close' event.
//
// The socket and Http2Session lifecycles are tightly bound. Once one is
// destroyed, the other should also be destroyed. When the socket is destroyed
// with an error, session.destroy() will be called with that same error.
// Likewise, when session.destroy() is called with an error, the same error
// will be sent to the socket.
class Http2Session extends EventEmitter {
  constructor(type, options, socket) {
    super();

    // No validation is performed on the input parameters because this
    // constructor is not exported directly for users.

    // If the session property already exists on the socket,
    // then it has already been bound to an Http2Session instance
    // and cannot be attached again.
    if (socket[kBoundSession] !== undefined) {
      throw new ERR_HTTP2_SOCKET_BOUND();
    }

    socket[kBoundSession] = this;

    // Node.js wraps non-handle-backed streams (e.g. duplexPair Duplex
    // streams used in tests) with JSStreamSocket so its native nghttp2
    // binding has a uniform stream interface. Deno's polyfill drives
    // nghttp2 entirely from JS (see setupHandle's socket.on("data") /
    // socket.write), so any Duplex with on/write is usable directly.
    socket.on("error", socketOnError);
    socket.on("close", socketOnClose);

    this[kState] = {
      destroyCode: NGHTTP2_NO_ERROR,
      flags: SESSION_FLAGS_PENDING,
      goawayCode: null,
      goawayLastStreamID: null,
      sentGoawayCode: null,

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Keep a strict 1:1 socket-to-session mapping: cache and reuse the http2session, never the bare socket
  2. For server-initiated outbound traffic, create a fresh http2.connect() with its own socket instead of reusing the client socket
  3. If you need another session, open a new connection (new TLS/TCP socket) first
  4. In tests, create a fresh duplex/socket per session

Example fix

// before
const s1 = http2.connect("https://a.example", { createConnection: () => sock });
const s2 = http2.connect("https://b.example", { createConnection: () => sock }); // throws

// after
const s1 = http2.connect("https://a.example", { createConnection: () => sock });
const s2 = http2.connect("https://b.example"); // own new socket
Defensive patterns

Strategy: try-catch

Validate before calling

// No public property exposes the binding; prevent instead of detect:
// keep one cached http2session per socket and reuse the session, never the socket.
const session = socketPool.get(socket) ?? http2.connect(authority, { createConnection: () => socket });
socketPool.set(socket, session);

Type guard

function assertSocketFree(socket) {
  // heuristic: a socket carrying an h2 session emits 'session'/has session listeners
  if (socket.listenerCount("session") > 0) {
    throw new Error("socket already bound to an Http2Session");
  }
}

Try / catch

try {
  session = http2.connect(authority, { createConnection: () => socket });
} catch (err) {
  if (err.code === "ERR_HTTP2_SOCKET_BOUND") {
    session = http2.connect(authority); // fresh socket, fresh session
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling http2.connect(..., { createConnection: () => sharedSocket }) twice with the same socket; building an outgoing session over a server-side socket that already has the server session; wrapping an already-wrapped TLS/tnet socket.

Common situations: Proxy/tunnel code that reuses the incoming socket for an upstream h2 session; connection-pool implementations caching sockets but not sessions; test fixtures reusing a single duplex (duplexPair) across tests.

Related errors


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