Documentation
¶
Overview ¶
Package fslock provides an advisory, cross-process file lock used to serialize read-modify-write cycles over the CLI's on-disk state.
Why it exists ¶
Every mutation of keys.json (the registry), keystore.json (the file vault), and config.json is a load → modify → atomic-rename cycle. The atomic rename prevents a torn file, but it does NOT prevent a lost update: two concurrent korbit-cli processes — the expected case is a long-running monitor bot plus ad-hoc human commands — can each read the same snapshot and last-writer-wins the whole file. For the file vault that can silently drop a freshly-added key's PRIVATE material while its registry record survives (unrecoverable; forces a portal rotation). An advisory lock held across the whole read-modify-write closes that window.
The lock ¶
Lock takes an EXCLUSIVE advisory lock on a dedicated sidecar file (e.g. "<home>/keys.json.lock") — never on the data file itself, so the data file's atomic-rename is undisturbed. The lock is advisory: it only excludes other callers that also go through Lock on the same path. Reads that rely on the atomic rename for a consistent snapshot need no lock.
Blocking acquire is intentional. These critical sections are milliseconds (load a small JSON file, mutate, atomic-rename), so a contending process simply waits its turn rather than failing.
Crash safety ¶
OS advisory locks (flock on unix, LockFileEx on Windows) are released automatically when the owning process exits or its file descriptor/handle is closed — including on a crash or kill. So there is NO stale-lock recovery to do and the lock file is never deleted by the CLI (deleting it would race a concurrent acquirer holding it open). A leftover ".lock" file is expected and harmless; it carries only a human-readable note (see lockNote) explaining that a user is free to delete it whenever no korbit-cli process is running.
Lock ordering (callers' contract) ¶
fslock itself imposes no ordering, but its callers must: the only nested acquisition in this codebase is keys.Manager holding the registry lock (keys.json.lock) while it drives the file vault's Set/Delete, which take the SEPARATE vault lock (keystore.json.lock). That nesting is safe ONLY because the two locks are different files. The fixed order is registry → vault, never the reverse, and nothing in the vault layer may acquire the registry lock — otherwise two processes could deadlock. See internal/keys/doc.go and internal/keystore/doc.go.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Lock ¶
Lock acquires an exclusive advisory lock on the sidecar file at path (conventionally "<datafile>.lock"), creating it 0600 if needed. The parent directory is created 0700 first, because the lock may be taken before the CLI home exists (`key add` creates it on first use). It blocks until the lock is held, then returns a release func the caller MUST defer; release unlocks and closes the descriptor/handle. A non-nil error means the lock was NOT acquired and release is nil.
Types ¶
This section is empty.