denoland/deno · error · Error
RoundRobinHandle.add: worker already added
Error message
RoundRobinHandle.add: worker already added
What it means
In cluster round-robin mode (the default off-Windows), the primary tracks which workers belong to each listening server in a RoundRobinHandle keyed by worker.id. add() throws a plain Error if that id is already registered — an internal invariant violation meaning the same worker was added to the same handle twice, which in practice means duplicated listening messages for one (address, port) query.
Source
Thrown at ext/node/polyfills/internal/cluster/round_robin_handle.ts:92
this.handle.onconnection = (err: number, handle: any) =>
this.distribute(err, handle);
if (this.handle instanceof TCP) {
setupTCPListenWrap(this.handle);
} else if (this.handle instanceof Pipe) {
setupPipeListenWrap(this.handle);
}
this.server._handle = null;
this.server = null;
});
}
RoundRobinHandle.prototype.add = function (
this: any,
worker: any,
send: (errno: number | null, reply: any, handle: any) => void,
) {
if (this.all.has(worker.id)) {
throw new Error("RoundRobinHandle.add: worker already added");
}
this.all.set(worker.id, worker);
const done = () => {
if (this.handle.getsockname) {
const out: any = {};
this.handle.getsockname(out);
send(null, { sockname: out }, null);
} else {
send(null, null, null); // UNIX socket.
}
this.handoff(worker); // In case there are connections pending.
};
if (this.server === null) {
return done();
}View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Listen once per port per worker; fork a fresh worker instead of re-listening inside the same one
- Do not pass explicit ids when forking replacement workers; let cluster assign them
- If it persists, return to stock cluster.fork() usage and remove custom setupPrimary hooks that touch internals
Example fix
// before (inside one worker)
http.createServer(handler).listen(8080);
http.createServer(handler2).listen(8080); // same worker + port -> duplicate add
// after
const server = http.createServer(handler);
server.on("request", handler2); // one listener per port per worker
server.listen(8080); Defensive patterns
Strategy: validation
Validate before calling
// one listen per (worker, port)
const listening = new Set();
function listenOnce(server, port) {
if (listening.has(port)) {
throw new Error(`worker already listening on ${port}`);
}
listening.add(port);
server.listen(port);
} Prevention
- Call listen() exactly once per port inside each cluster worker
- Let cluster assign worker ids; never reuse ids across fork cycles
- Avoid custom setupPrimary hooks that touch internal handle bookkeeping
When it happens
Trigger: A single cluster worker calling server.listen() twice on the same port (two listening messages, same query, same worker.id); custom re-fork logic that reuses worker ids while the old registration still exists in the primary.
Common situations: Starting two servers on the same port inside one worker under cluster; worker-restart scripts that fork replacements with explicit ids; migrations where the primary's handle bookkeeping desynced from reality.
Related errors
- SharedHandle.add: worker already added
- Unexpected third argument to Deno.bench()
- The benchmark which this context belongs to is not being exe
- Bad cluster.schedulingPolicy: ${schedulingPolicy}
- Unknown worker event: "${type}"
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/98c37019176e1e91.
Report an issue: GitHub.