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
- Keep a strict 1:1 socket-to-session mapping: cache and reuse the http2session, never the bare socket
- For server-initiated outbound traffic, create a fresh http2.connect() with its own socket instead of reusing the client socket
- If you need another session, open a new connection (new TLS/TCP socket) first
- 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
- Maintain a strict 1:1 socket-to-session mapping in connection pools; pool sessions, not bare sockets
- Never build an outbound session over a server-side socket that already hosts the server session
- Create a new socket whenever you need an additional http2.connect()
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
- ERR_FS_CP_SOCKET
- ERR_HTTP2_NO_SOCKET_MANIPULATION
- ERR_HTTP2_SOCKET_UNBOUND
- ERR_HTTP2_HEADERS_SENT
- ERR_INVALID_ARG_TYPE
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/fb405dcc2d0f064b.
Report an issue: GitHub.