decolua/9router · warning · Error

MITM server is already starting (lock contention)

Error message

MITM server is already starting (lock contention)

What it means

9Router's MITM manager serializes concurrent starts across processes with a lock file created atomically via fs.writeFileSync(..., { flag: "wx" }) (O_EXCL). When another live process already holds the lock (its PID is readable from the lock file and that PID is still alive), startServer throws 'MITM server is already starting (lock contention)'. Only stale locks (dead PID or unreadable file) are cleaned up and reclaimed.

Source

Thrown at src/mitm/manager.js:504

    } catch { /* ignore */ }
  }

  if (serverProcess && !serverProcess.killed) {
    throw new Error("MITM server is already running");
  }

  // Atomically claim lock to prevent concurrent startServer across processes.
  // O_EXCL (flag: "wx") fails with EEXIST if the file already exists.
  try {
    fs.writeFileSync(LOCK_FILE, String(process.pid), { flag: "wx" });
  } catch (e) {
    if (e.code === "EEXIST") {
      let stale = false;
      try {
        const pid = parseInt(fs.readFileSync(LOCK_FILE, "utf-8").trim(), 10);
        stale = !pid || !isProcessAlive(pid);
      } catch { stale = true; } // unreadable lock → treat as stale
      if (!stale) throw new Error("MITM server is already starting (lock contention)");
      try { fs.unlinkSync(LOCK_FILE); } catch { /* ignore */ }
      fs.writeFileSync(LOCK_FILE, String(process.pid), { flag: "wx" });
    } else throw e;
  }

  try {
    await killLeftoverMitm(sudoPassword);

  if (!IS_WIN) {
    const portStatus = await checkPort443Free();
    if (portStatus === "in-use" || portStatus === "no-permission") {
      const owner = await getPort443Owner(sudoPassword);
      if (owner) {
        const shortName = owner.name.includes("/")
          ? owner.name.split("/").filter(Boolean).pop()
          : owner.name;
        if (forceKillPort443) {
          log(`Killing process on port 443 (PID ${owner.pid}, name=${shortName})...`);

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Wait a few seconds and retry — the owning process finishes start and releases the lock; meanwhile check its health endpoint on the MITM port instead of starting again
  2. Find the process holding the lock: read the PID from the lock file under ~/.9router (MITM_DIR) and inspect `ps -p <pid>`; kill it if it is a hung duplicate
  3. Delete the stale-looking lock file manually if the PID is dead or belongs to nothing, then retry start (the code also self-heals stale locks)
  4. Ensure only one 9Router instance runs per machine/DATA_DIR; for multi-instance use separate DATA_DIR values

Example fix

// before: blind retry loop hammering start
await startMitm();
// after: check if already running before starting
const health = await fetch('http://127.0.0.1:<MITM_PORT>/health').then(r => r.ok).catch(() => false);
if (!health) await startMitm();
Defensive patterns

Strategy: retry

Validate before calling

const fs = require('fs');
const LOCK = require('os').homedir() + '/.9router/mitm/start.lock'; // match MITM_DIR lock path
function lockHolderAlive() {
  try {
    const pid = parseInt(fs.readFileSync(LOCK, 'utf-8').trim(), 10);
    try { process.kill(pid, 0); return pid; } catch { return null; }
  } catch { return null; }
}

Try / catch

const backoff = [500, 1500, 4000];
for (const ms of backoff) {
  try { await startMitm(apiKey); break; }
  catch (e) {
    if (!/lock contention/i.test(e.message)) throw e;
    await new Promise(r => setTimeout(r, ms));
  }
}

Prevention

When it happens

Trigger: Calling startServer (or an API/CLI path that starts the MITM proxy) while another 9Router process — dashboard server, CLI instance, or a previous uncrashed spawn — is mid-start and holds the lock; two clients racing the start endpoint simultaneously; a sibling process that wrote the lock then hung before removing it.

Common situations: Running `9router` CLI and the dashboard dev server at once and both triggering MITM start; double-clicking a tray/start action firing two starts; a previous start attempt crashed after writing the lock but its PID is still alive (hung process); Docker/multi-instance setups sharing the same MITM_DIR (~/.9router) on one machine.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/27b4e1403f93a775. Report an issue: GitHub.