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

  1. Listen once per port per worker; fork a fresh worker instead of re-listening inside the same one
  2. Do not pass explicit ids when forking replacement workers; let cluster assign them
  3. 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

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


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