Documentation
¶
Overview ¶
Package procsig delivers signals to agent processes guarded by their start epoch (DDR-5, GAPI-DIV-016): a signal aimed at a dead process whose PID was recycled must never hit the new occupant. Linux-only by design (operator decision 2026-07-28) - delivery uses pidfd_open + pidfd_send_signal with no fallback; non-Linux builds compile but refuse to deliver.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrStaleEpoch: the process at this PID is not the one the caller // meant (start epoch mismatch - the PID was recycled). ErrStaleEpoch = errors.New("procsig: stale start epoch, refusing delivery") // ErrProcessGone: no process holds this PID. ErrProcessGone = errors.New("procsig: process gone") // ErrUnsupported: signal delivery is Linux-only. ErrUnsupported = errors.New("procsig: signal delivery is linux-only") // ErrPidfdUnsupported: the kernel does not offer pidfd_open, so // neither this package nor the os/exec handle path can pin a // process. Signal delivery would silently degrade to kill-by-PID, // which is the PID-recycling hazard itself (GAPI-DIV-016). ErrPidfdUnsupported = errors.New("procsig: kernel does not support pidfd_open") )
Typed delivery failures. Errors are data; there is deliberately no retry on ErrStaleEpoch - a stale epoch means the target is gone and the orchestrator must re-resolve, not hammer a recycled PID.
Functions ¶
func RequirePidfd ¶
func RequirePidfd() error
RequirePidfd reports whether this kernel can pin a process with pidfd_open, by opening a pidfd on this process and closing it again.
It exists because gapi's signal safety is INHERITED rather than asserted (GAPI-DIV-016). os.Process signals through pidfd_send_signal against a handle os/exec bound at fork, which is a stronger guarantee than this package's epoch check - a handle cannot refer to a recycled PID at all. But if the kernel has no pidfd, os/exec silently falls back to a raw kill by PID and nothing anywhere notices. A supervisor that signals the wrong process because the kernel was older than it assumed should refuse to boot, not discover it during a stop.
Probing self is deliberate: it needs no target to exist and no privilege beyond signalling ourselves, so a false negative cannot come from the target rather than the kernel.
func Signal ¶
Signal delivers sig to pid if and only if the process's start epoch matches. The check-pin-recheck order makes it race-free: the epoch is checked, the process is pinned with pidfd_open, and the epoch is re-checked while pinned - a PID recycled between the first check and the pin shows a different epoch on the recheck and is refused. The signal then goes to the pinned process via pidfd_send_signal, never to a raw PID.
func StartEpoch ¶
StartEpoch returns the process's start time in clock ticks since boot (/proc/<pid>/stat field 22). Recorded at spawn, it uniquely identifies a PID incarnation on one boot of one node.
Types ¶
type ProcessIdentity ¶
ProcessIdentity is the mutable runtime locator of a process: the PID plus the two fields that disambiguate a PID across recycling and namespaces (DDR-3/4/5). It travels in gossip, never in Raft.
func Identify ¶
func Identify(pid int) (ProcessIdentity, error)
Identify captures a process's runtime locator: pid, start epoch, and pid-namespace inode (/proc/<pid>/ns/pid). Nodes call this after spawning an instance and publish it over gossip.