Expand description
The server’s pid file: <AION_HOME>/run/aion-server.pid.
The file is the stop/status verbs’ address for the running server. It is
written at BIRTH — before the store is opened, before anything can take
minutes — and it carries the incarnation’s whole life: the state it is in
(IncarnationState), the boot stage it is working through, the
addresses once they are bound, and the drain window once it is known.
RULING — the record appears at birth, not at bind. A big store takes
minutes of WAL recovery, and a record written only at bind left every
control verb blind for that whole window: status said “no server has
claimed this home”, stop said “nothing to stop” (exit 0), and the
launcher’s port probe read the home as empty and spawned ANOTHER server,
which blocked silently on the store writer lock. Four servers stacked
invisibly on 2026-08-26. The birth claim is what makes that impossible:
the second boot’s claim_at_birth sees a
live booting sibling and REFUSES.
Every mutation of the file — the birth claim’s rename-into-place, each
stage write, the bind-time fill, the drain flip, the stop verb’s
compare-and-delete, the guard’s own exit-time compare-and-delete — runs
under an exclusive OS file lock (std::fs::File::lock: flock on
Unix, LockFileEx on Windows) on a sibling lock file
(aion-server.pid.lock). The lock is what makes compare-and-delete
atomic: without it, a successor claiming the home between a remover’s
read and its unlink would have ITS record deleted, leaving a live server
no verb can address. The lock file itself is never renamed or removed —
locking the pid file directly would be unsound, because the claim
replaces that path’s inode and a lock on the old inode excludes nobody.
RULING — a lock failure at claim time REFUSES the boot. The lock is the instrument that keeps one home’s records from destroying each other; a home where it cannot be taken (permissions on the lock file, a filesystem without advisory locking) is a home where a claim can silently delete a live server’s address, and the honest answer is a refusal naming the remedy, not a boot that runs without the guarantee. The exit-time guard is deliberately more lenient (it leaves the file for stale reconciliation) because at that point refusing helps nobody — the process is exiting either way.
RULING — every future field added to PidRecord is #[serde(default)]
on read. The record is a cross-process, cross-version contract: a newer
CLI must be able to read the record an older running server wrote —
upgrade time is exactly when the stop verb matters most — so a missing
field reads as its honest default, never as a parse refusal.
RULING — records are compared by INCARNATION IDENTITY (pid + start
instant), never by whole-record equality. The record now MUTATES during
the incarnation’s life (every stage write bumps stage_seq), so a
whole-record compare would make the stop verb’s reconciliation and the
guard’s exit-time delete miss their own record the moment a stage landed
between the read and the compare — leaving live-looking debris behind
every boot that took long enough to report progress.
The record is an incarnation identity, never a bare pid: pid, start
instant, and the serving binary’s content hash (plus the bound addresses
and build identity, so readers talk to the server that IS running rather
than the one today’s config would start). A stale file found at claim
time is reconciled by incarnation check and reported — never silently
overwritten, never trusted. See crate::control::incarnation for how a
record is verified against the live process table.
Structs§
- PidRecord
- The pid file’s content: one JSON object describing the server incarnation that claimed the home.
Enums§
- Incarnation
State - Where a recorded incarnation is in its own life.
- Stale
Reconciliation - What the birth claim found already sitting at the pid-file path, reconciled before the new record was written. Reported to the operator through the boot log; returned so tests can assert the reconciliation happened rather than trusting the log.
Functions§
- now_
unix_ secs - Whole seconds since the Unix epoch, or 0 when the system clock is set before the epoch (which no reader can do arithmetic on anyway).
- pid_
file_ path - Absolute path of the pid file under
home. - read
- Read and parse the pid file under
home. - remove_
if_ matches - Remove the pid file under
homeif — and only if — it still holds the SAME INCARNATION asrecord. Used by the stop verb to reconcile the file a non-clean exit (forced, killed) left behind, after the process is proven gone.