Expand description
Single-instance boot guard.
forge guarantees one instance per config dir. crate::Workspace::new
calls acquire at startup, taking a non-blocking exclusive flock
on a never-renamed lockfile. If another forge already holds it, boot
refuses with an AcquireError::AlreadyRunning: a clean stderr
message + non-zero exit, not a panic. Otherwise the callerβs PID is
written and the lock is held for the process lifetime (flock
auto-releases on exit/crash, so there is no stale-lock cleanup). One
instance per config dir is what lets the rest of forge - the cron
store especially - coordinate with just an in-process mutex instead
of cross-process locking.
The lockfile is MACHINE-LOCAL: it lives under forgeβs app-support base
(forge_sdk::app_support_dir, beside the logs) at
locks/<hash>.lock, where <hash> is derived from the config-dir
path so distinct profiles / config dirs on one machine get distinct,
independently-held locks.
Why there and not beside the other forge state in <config_dir>/forge/:
flock binds the open file description (the inode), not the path, and
rewriting a file by renaming a temp over it swaps the inode, so the lock
must be a dedicated file that is only ever opened + flocked, never
renamed. The config dir is Syncthing-synced across the userβs Macs, and
Syncthing applies incoming changes by rename - a lock synced in from
another Mac would swap the inode out from under a running forge,
orphaning its flock and letting a second instance start. A machine-local
lock outside the synced dir sidesteps it.
A v0.18.0 boot may have left a stray <config_dir>/forge/forge.lock; it
is now unused and harmless (a fresh boot writes the machine-local lock
instead), so there is no migration.
Re-exportsΒ§
use std::fs::File;πuse std::fs::OpenOptions;πuse std::io::Read;πuse std::io::Seek;πuse std::io::Write;πuse std::path::Path;πuse std::path::PathBuf;πuse rustix::fs::FlockOperation;πuse rustix::fs::flock;π
EnumsΒ§
- Acquire
Error π - Why
acquirerefused.
ConstantsΒ§
- LOCK_
DIR_ πNAME - Subdirectory of the app-support base that holds the per-config-dir lockfiles.
FunctionsΒ§
- acquire π
- Acquire the per-config-dir single-instance lock under
app_support. - another_
instance_ πrunning - Whether another live forge holds a lock under
app_supportfor a config dir other thanconfig_dir. - lock_
path π - Machine-local lock path for
config_dirunderapp_support:<app_support>/locks/<hex-hash-of-config-dir>.lock. The config-dir path is canonicalised first (best-effort) so symlinked / trailing-slash variants resolve to one lock. - read_
pid π - Read the holderβs PID from the lockfile.
Nonewhen empty or unparseable. - record_
pid π - Stamp our PID into the (now exclusively-held) lockfile so a would-be second instance can name us in its refusal. Best-effort: a failed write doesnβt void the lock - the flock is what enforces single-instance; the PID is only for the message.