Documentation
¶
Overview ¶
Package openfiles answers the one question every reclaimer that deletes from a shared directory must ask first: is a live process using this path?
Three reclaimers ask it — the temp sweep, source-cache eviction and worktree removal — and each had its own lsof parser, which went wrong in the same three ways:
- PATH SPELLING. lsof reports the path the kernel resolved, so on macOS a file under os.TempDir() (/var/folders/…) is reported as /private/var/folders/…. A string comparison against the unresolved form matched 2 of 266 open files under $TMPDIR on one Mac: the in-use check was off and nothing said so. Snapshot compares canonical paths on BOTH sides.
- PARTIAL SNAPSHOTS. A timed-out or killed lsof has already written part of its listing. Treating that prefix as the whole machine reports every process it never reached as idle. Take accepts only a listing lsof finished: exit 0, or lsof's routine exit 1 for an inaccessible process.
- NO TIMEOUT. One caller ran lsof unbounded, so a wedged lsof hung `forge env up`. Take always runs under Timeout.
The /proc source (openfiles_proc.go) avoids the same three by construction: it feeds FromPaths, scans every process before answering or fails, and runs under Timeout.
Every failure is an error, and every caller treats an error as "everything is in use". Without the snapshot, age is the only evidence left, and age cannot tell abandoned from merely quiet.
Index ¶
Constants ¶
const SourceEnv = "FORGE_OPENFILES_SOURCE"
SourceEnv names the environment variable that, set to "lsof", makes Take use lsof on Linux instead of /proc: an escape hatch for a host whose procfs is restricted in a way lsof is not (and the seam the tests use to drive a fake lsof).
const Timeout = 2 * time.Minute
Timeout bounds one snapshot. lsof takes seconds on a busy developer machine (~6s measured across ~74k records); minutes means it is wedged on a hung mount, and an answer that never arrives is no better than a wrong one.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Snapshot ¶
type Snapshot struct {
// contains filtered or unexported fields
}
Snapshot is the set of paths live processes held at one instant: open files, working directories, mapped executables. The zero value holds nothing.
func FromPaths ¶
FromPaths builds a snapshot from paths already known to be open. Take uses it for lsof's output; tests use it to state exactly what is open.
func Take ¶
Take records one machine-wide snapshot: from /proc on Linux, with lsof on other Unixes, lazily on Windows.
Linux reads /proc rather than running lsof because lsof is itself only a reader of /proc there. Going to the source needs no binary (minimal images, containers and sandboxes ship none, and every caller fails closed without a snapshot, so a missing lsof meant nothing was ever reclaimed), no subprocess whose output can be cut short, and no parsing of a second format. macOS has no /proc, so lsof stays the source there.
One snapshot rather than a probe per candidate: a per-path `lsof +D` would race — a path could be opened between its own probe and its removal.