fix: refuse to start over another port's server in a shared state dir #73
No reviewers
Labels
No labels
area:defold
area:docs
area:infra
area:protocol
area:testing
kind:bug
kind:chore
phase:breadth
phase:core
phase:hardening
phase:slice
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
yuris/spacetimedb-defold!73
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dev/63-shared-state-guard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #63.
Point two ports at one explicit
STDB_STATE_DIRand the secondstart_serverfails SpacetimeDB's database lock — correctly, the lock is exclusive — but it still wrote$PIDFILEwith its own doomed child's pid. After that,stopon the first port read a dead pid, reportedno server running, and left a live server nothing could find again.server_pid()cannot repair this after the fact: the first pid is already gone from the file by the time anyone asks. So the fix refuses the overwrite, before anything is spawned.The guard reads
/proc/$pid/cmdlinefor the pid already in the pidfile — the same readserver_pid()already does — and aborts if it belongs to a live server on a different port. Same trailing-space match as the existing check (it stops port692matching a server on6921), and the same degradation: without/proc, the guard is inert and behaviour is exactly what it was.It sits at the top of
start_server, before the keypair generation as well as the spawn, so a refused invocation writes nothing at all into the contested state dir.Reproduced first, then fixed
On
main@a606406, two ports sharing/tmp/63-test:The leak, exactly as the issue describes:
On this branch, same sequence:
It also fails in 2.08s instead of 31.59s, because it no longer waits out a ping timeout on a server that was never going to start. And
stopnow works:Untouched paths confirmed
server already running at http://127.0.0.1:6921 (pid …)and proceeds to publish.200, both stopped cleanly.nix run nixpkgs#shellcheck -- dev/tools/dev-server.shexits 0 with no output. Lua suite andstylua --checkunaffected (7 passed, 0 failed), as expected for a shell-only change.One scope note: the guard keys on the pid recorded in the pidfile, so if the first server is already stopped and the pidfile is merely stale, the second port proceeds and hits SpacetimeDB's own database lock. That is unchanged and correct — that error is legible, and it no longer destroys a live server's record on the way.
Both findings actioned. The first was a real hole and the review is right that it mattered more than a corner case.
restartbypassed the guard.stop_serverends in an unconditionalrm -f "$PIDFILE", andrestart)called it beforestart_server— so the first port's record was deleted before the guard could read it, and #63 reproduced verbatim.restartis also what both conformance harnesses drive, so that was the common path, not the rare one.The guard is now a function,
refuse_foreign_state_dir(), sitting besideserver_pid()and called from two places: the first statement ofstart_server, and the first statement ofrestart)beforestop_server.stop_server'srm -fis left alone so stale-pidfile cleanup keeps working, andstop)needs nothing since it only callsstop_serverwhenserver_pidalready matched this port.Reproduced the bypass and then its absence, ports 6951/6952 sharing
/tmp/63-restart:Second finding: the
mkdir -pclaim was false. Correct, and now true rather than reworded — the guard runs beforemkdir -p, as the first statement ofstart_server, since it only reads$PIDFILE.Same-port paths re-verified, because moving the check into
restartput the harnesses' own path at risk:shellcheckstill exits 0. Native conformance passes on the branch (all six phases), which exercises the same-portrestartpath twice per run.