{"record":{"id":"7d6b20c9373dcaa8","repo":"denoland/deno","slug":"sharedhandle-add-worker-already-added","errorCode":null,"errorMessage":"SharedHandle.add: worker already added","messagePattern":"SharedHandle\\.add: worker already added","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/internal/cluster/shared_handle.ts","lineNumber":64,"sourceCode":"  } else {\n    this.handle = rval;\n    // Match Node: leave listen() to the worker. We rely on `_createServerHandle`\n    // (and below it `uv_tcp_bind`) reporting EADDRINUSE synchronously, so the\n    // primary doesn't need to enter LISTEN state to discover bind errors.\n    // Keeping the primary out of LISTEN avoids it sitting on the kernel\n    // accept queue for the shared port (it never calls accept()) and avoids\n    // surprising interactions with later bind() calls in the primary process\n    // -- e.g. an ipv6Only listener on `::` blocking a sibling 0.0.0.0 bind.\n  }\n}\n\nSharedHandle.prototype.add = function (\n  this: any,\n  worker: any,\n  send: (errno: number, reply: null, handle: any) => void,\n) {\n  if (this.workers.has(worker.id)) {\n    throw new Error(\"SharedHandle.add: worker already added\");\n  }\n  this.workers.set(worker.id, worker);\n  send(this.errno, null, this.handle);\n};\n\nSharedHandle.prototype.remove = function (this: any, worker: any) {\n  if (!this.workers.has(worker.id)) {\n    return false;\n  }\n\n  this.workers.delete(worker.id);\n\n  if (this.workers.size !== 0) {\n    return false;\n  }\n\n  this.handle.close();\n  this.handle = null;","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/internal/cluster/shared_handle.ts#L46-L82","documentation":"When schedulingPolicy is SCHED_NONE (or the platform uses shared sockets), the primary shares one listening handle among workers and registers each worker in SharedHandle.workers. add() throws a plain Error when the same worker.id registers twice for the same (address, port, addressType) query — the same duplicate-add invariant as the round-robin path, enforced in the shared-socket bookkeeping.","triggerScenarios":"cluster.schedulingPolicy = cluster.SCHED_NONE with a worker that sends two listening messages for the same port (double listen() in one worker); duplicate worker ids from custom fork logic.","commonSituations":"Windows deployments or explicit SCHED_NONE with repeated listen() calls; test harnesses that start and restart servers repeatedly inside a long-lived worker.","solutions":["Listen once per port per worker; consolidate handlers onto a single server instance","Avoid reusing worker ids across fork cycles","Reproduce with the default round-robin policy to confirm the root cause is a double listen"],"exampleFix":"// before\nconst a = net.createServer();\nconst b = net.createServer();\na.listen(9000);\nb.listen(9000); // second listening message from the same worker\n\n// after\nconst server = net.createServer();\nserver.listen(9000); // one listening server per port per worker","handlingStrategy":"validation","validationCode":"const listening = new Set();\nfunction listenOnce(server, port) {\n  if (listening.has(port)) {\n    throw new Error(`worker already listening on ${port}`);\n  }\n  listening.add(port);\n  server.listen(port);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Consolidate handlers onto one server per port in each worker","Do not re-listen on the same port after restart inside a live worker; fork fresh","Test with both scheduling policies when adding multi-server workers"],"tags":["cluster","shared-sockets","internal-invariant"],"backgroundTag":"duplicate-worker-registration","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}