{"record":{"id":"fb405dcc2d0f064b","repo":"denoland/deno","slug":"err-http2-socket-bound","errorCode":"ERR_HTTP2_SOCKET_BOUND","errorMessage":"The socket is already bound to an Http2Session","messagePattern":"The socket is already bound to an Http2Session","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/http2.ts","lineNumber":3993,"sourceCode":"// immediately following the 'close' event.\n//\n// The socket and Http2Session lifecycles are tightly bound. Once one is\n// destroyed, the other should also be destroyed. When the socket is destroyed\n// with an error, session.destroy() will be called with that same error.\n// Likewise, when session.destroy() is called with an error, the same error\n// will be sent to the socket.\nclass Http2Session extends EventEmitter {\n  constructor(type, options, socket) {\n    super();\n\n    // No validation is performed on the input parameters because this\n    // constructor is not exported directly for users.\n\n    // If the session property already exists on the socket,\n    // then it has already been bound to an Http2Session instance\n    // and cannot be attached again.\n    if (socket[kBoundSession] !== undefined) {\n      throw new ERR_HTTP2_SOCKET_BOUND();\n    }\n\n    socket[kBoundSession] = this;\n\n    // Node.js wraps non-handle-backed streams (e.g. duplexPair Duplex\n    // streams used in tests) with JSStreamSocket so its native nghttp2\n    // binding has a uniform stream interface. Deno's polyfill drives\n    // nghttp2 entirely from JS (see setupHandle's socket.on(\"data\") /\n    // socket.write), so any Duplex with on/write is usable directly.\n    socket.on(\"error\", socketOnError);\n    socket.on(\"close\", socketOnClose);\n\n    this[kState] = {\n      destroyCode: NGHTTP2_NO_ERROR,\n      flags: SESSION_FLAGS_PENDING,\n      goawayCode: null,\n      goawayLastStreamID: null,\n      sentGoawayCode: null,","sourceCodeStart":3975,"sourceCodeEnd":4011,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/http2.ts#L3975-L4011","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst s1 = http2.connect(\"https://a.example\", { createConnection: () => sock });\nconst s2 = http2.connect(\"https://b.example\", { createConnection: () => sock }); // throws\n\n// after\nconst s1 = http2.connect(\"https://a.example\", { createConnection: () => sock });\nconst s2 = http2.connect(\"https://b.example\"); // own new socket","handlingStrategy":"try-catch","validationCode":"// No public property exposes the binding; prevent instead of detect:\n// keep one cached http2session per socket and reuse the session, never the socket.\nconst session = socketPool.get(socket) ?? http2.connect(authority, { createConnection: () => socket });\nsocketPool.set(socket, session);","typeGuard":"function assertSocketFree(socket) {\n  // heuristic: a socket carrying an h2 session emits 'session'/has session listeners\n  if (socket.listenerCount(\"session\") > 0) {\n    throw new Error(\"socket already bound to an Http2Session\");\n  }\n}","tryCatchPattern":"try {\n  session = http2.connect(authority, { createConnection: () => socket });\n} catch (err) {\n  if (err.code === \"ERR_HTTP2_SOCKET_BOUND\") {\n    session = http2.connect(authority); // fresh socket, fresh session\n  } else {\n    throw err;\n  }\n}","preventionTips":["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()"],"tags":["http2","node-compat","socket","session-management","deno"],"backgroundTag":"socket-already-in-use","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}