Files
boxmaker/docs/plans/M5b/files/runbook-already-running.md
kyleandClaude Opus 5.5 291d82f21b M5b plan: task 12, instance lock in bxcore, loopd and inferproxy
bxcore::instance::lock (File::try_lock, file 0600, missing directory
0700), LockError::line for the two lines the daemons print, and the
already-running runbook fragment; loopd locks <home>/run/loopd.lock
before its socket and self-test, inferproxy <socket>.lock.

Checks: given tests fail at ref 11 as the task says (bxcore E0432;
loopd 3 and inferproxy 3 fail); the task's quoted edits applied to
ref 11 reproduce ref 12 byte for byte; replay gate ok with 1113 tests
(1099 + 14), /tmp unchanged; 25 mutations, 24 caught (the uncaught
one maps TryLockError::Error to Held, not reachable from a test);
given files rustfmt-clean, under 500 lines; no chat-template tags.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 17:21:14 -07:00

48 lines
2.6 KiB
Markdown

## already-running
**What you see.** A daemon exits 1 at start, before it touches its sockets, with one of these
lines:
- `<role>: another <role> is running (it holds <lock file>); see docs/runbook.md#already-running`
- `<role>: cannot take the lock <lock file>: <error>; see docs/runbook.md#already-running`
`<role>` is `loopd`, `brokerd`, `memoryd`, `gatewayd` or `inferproxy`. The lock file is
`$BOXMAKER_HOME/run/<role>.lock`; for `inferproxy` it is its `--listen` socket's path with
`.lock` added (`infer.sock.lock`, beside `infer.sock`).
**Why.** Every daemon removes a socket it finds at its path before it binds, so that a socket
left by a daemon that was killed never blocks the next start. A second copy started by mistake
would therefore take the sockets of the one that runs, which would keep running, unreachable,
and nothing would say so. So each daemon first takes an exclusive lock on its lock file, and
holds it for as long as it runs (M5b spec, section 9a). The system releases the lock when the
process ends, however it ends, so a killed daemon never leaves a lock behind. The file stays in
place and holds nothing.
**Confirm.** For `another <role> is running`, find the copy that runs, with its command line
(the `--config` path says which home it serves):
```sh
pgrep -a -x <role>
ls -l "$BOXMAKER_HOME/run/<role>.lock"
```
For `cannot take the lock`, the error says why: `Permission denied` means the daemon's user
cannot create or open the file, or create `run/` (`ls -ld "$BOXMAKER_HOME" "$BOXMAKER_HOME/run"`
and `ls -l` on the lock file show who owns them); `Not a directory` means a path above it is a
file; `Read-only file system` means what it says.
**Fix.** Two copies of a role against the same home are always a mistake: use the one that runs.
To restart it, stop it (`pkill -x <role>`, or Ctrl-C in its terminal), wait until `pgrep -x
<role>` prints nothing, and start it again. Two copies with different homes are fine (each has
its own lock), as long as their configs do not name the same socket path. For a lock that cannot
be taken, give the daemon's user the directory (`chown`), or point its home somewhere it can
write.
Never delete a lock file while its daemon runs: the lock is held on the open file, so a new copy
would create a new file, lock that, and take the sockets. Deleting it while the daemon is stopped
does no harm, and does not help either.
**Check.** `pgrep -x <role>` prints one process id, and the daemon prints its start line
(`loopd: serving on …`, `inferproxy: <socket> -> <upstream>`, `brokerd: serving tools on …`,
`memoryd: serving`, `gatewayd: connected to …`).