Documentation
¶
Overview ¶
Package runnerbusy answers one question from ground truth rather than inference: is a GitHub Actions runner executing a job RIGHT NOW?
ephemerd otherwise only learns that a runner is busy by processing an `in_progress` webhook. That is inference ABOUT busy-ness, not an observation of it: anything that delays, drops or reorders a delivery — and a burst of same-label jobs makes all three likely — leaves a runner that is genuinely executing a build looking idle in the scheduler's ledger. Teardown decisions taken on that belief kill live builds.
The signal used here is the actions-runner's own process model. The listener process (`Runner.Listener`) runs for the whole life of the runner, so "a runner process exists" says nothing. The listener forks a `Runner.Worker` child ONLY while a job is executing, and reaps it when the job ends. "A worker exists" is therefore equivalent to "a job is running", and ephemerd — which owns the container, VM or process the runner lives in — can observe it locally: no network, no API budget, and immune to a missed or reordered webhook.
Every probe returns a State. Unknown is NOT idle. Callers MUST treat Unknown as "possibly busy" and fail safe; a probe that cannot answer never reports Idle.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrUnsupported = errors.New("runnerbusy: no local probe on this platform")
ErrUnsupported is returned by probes that have no implementation on the running platform. It is an explicit "I cannot answer" — never an implicit "not busy".
Functions ¶
func IsWorkerProcess ¶
IsWorkerProcess reports whether a process identifier names the actions-runner's job worker.
The argument is whatever the platform's process listing yields for a process: argv[0] on Linux (an absolute path such as /home/runner/bin/Runner.Worker), /proc/<pid>/comm as a fallback, or the HCS ImageName on Windows (Runner.Worker.exe). Matching is on the base name, case-insensitively, with a .exe suffix stripped, so one rule covers all three.
Runner.Listener is deliberately NOT matched: it is alive for the whole life of the runner, including while the runner sits idle waiting for a job, so matching it would make every runner look permanently busy.
Types ¶
type ContainerTask ¶
ContainerTask is the slice of containerd's client.Task that the container probe needs. Declared as an interface so the probe can be unit-tested without a containerd daemon.
ID returns the container ID (the init task's ID is the container's). Pids lists the processes in the container.
type State ¶
type State int
State is a probe's verdict about a runner.
const ( // Unknown means the probe could not determine the runner's state: // the platform has no local probe, the runtime refused the query, or // the answer was empty in a way that cannot be distinguished from a // failure. Callers must treat it as "possibly busy". Unknown State = iota // Idle means the probe positively observed that no job is executing: // the runner's process list was read successfully and contained no // worker. Only this verdict makes teardown safe. Idle // Busy means the probe positively observed a worker process, i.e. a // job is executing right now. Destroying the runner would kill it. Busy )
func ContainerBusy ¶
ContainerBusy reports whether the runner inside a containerd-managed Linux container is executing a job.
runc leaves container processes visible in the host's PID namespace, so the PIDs containerd reports are readable under /proc on the host. For each one we read argv[0] (falling back to the kernel's comm) and look for the runner's worker child.
Failure modes all resolve to Unknown, never Idle:
- the task query fails (shim gone, containerd restarting)
- the task reports no processes at all, which is indistinguishable from a listing that raced container teardown
- every /proc read failed, which means we are not in a position to see the container's processes (foreign PID namespace, hardened /proc) rather than that the container is quiet