/** * `daemon` — run the dispatch loop in the foreground; --once runs a single tick and exits. This is what `start` launches. * * Moved out of cli.ts's switch by the per-verb module split (#462); * only the case wrapper, the injected `ctx` lookups and the imports * changed from the original bodies. */ import type { CommandContext } from "./context.ts"; import { runDaemon } from "../daemon.ts"; import { clearRecord, publishForegroundRecord, startRecordRepair } from "../lifecycle.ts"; export async function daemonCommand(ctx: CommandContext): Promise { // Until now only `lifecycle.startDaemon()` — the spawn path — wrote the // pidfile, which left a daemon started in the foreground (which is how // systemd runs it) invisible twice over: `omp-conductor status` reported // no daemon at all, and a `daemon --once` drill run beside it saw // `livingDaemon() === undefined`, concluded nothing else was dispatching, // and reconciled the live daemon's in-flight runs as orphans. Writing the // record here closes both holes. const once = ctx.argv.includes("--once"); const port = ctx.portFlag(); const project = ctx.projectFlag; // A drill is still a dispatcher: it settles rows, projects labels, // admits, and launches workers, so it holds the same exclusive daemon/ // state ownership as the long-running dispatcher for the whole tick // (#431). `runDaemon` acquires that ownership up front (the one choke // point every once tick passes through) and refuses — having done no // mutation — while another live daemon owns the project; the thrown // refusal surfaces as `omp-conductor: another daemon is alive (pid N)…`. if (once) { await runDaemon({ once, port, project }); return; } // The boot half of pidfile ownership (#1114): a systemd-started or hand-run // foreground daemon publishes its own record in lifecycle — refusing, having // done nothing, while another live daemon owns the record. const claimed = publishForegroundRecord({ port, project }); if (claimed.kind === "foreign-live") { process.stderr.write(`omp-conductor: another daemon is alive (pid ${claimed.pid}); stop it first\n`); process.exit(1); } // The running half (#1114): while this process is the daemon it keeps its own // record alive, so an external sweep of the runtime directory cannot leave // the single-owner guard disarmed until the next restart. const repair = startRecordRepair({ port, project }); try { await runDaemon({ once, port, project }); } finally { // Stop repairing before clearing, so no interval tick can resurrect the // file after the removal below. repair.stop(); // The record names a pid that is about to stop existing. Leaving it // behind makes the next reader probe a ghost before believing us. clearRecord(); } }