Skip to content

Supervisor has no per-module resource visibility or bounds — one module's allocator hoard killed the entire daemon cgroup #16

Description

@iceteaSA

Feature ask grounded in a production incident, with the incident's numbers attached. Filed hub-side; the subconscious seat has the deep-triage custody and knows this thread exists.

The incident

2026-08-10 14:42, this box: systemd-oomd killed ck-subc.service. Unit accounting at the kill:

ck-subc.service: Consumed 19h 14min CPU over 1d 17h wall clock,
                 29.5G memory peak, 38.6G memory swap peak

The 29.5G was one module — aft's allocator hoard (mechanism established on cortexkit/aft#205: arena fragmentation, churn-bound growth). The kill took everything in the cgroup: the daemon, every supervised module regardless of health, and every child process the modules had spawned (including interactive seats parented through a module's shells). systemd restarted the service in 2s and the daemon rebuilt cleanly from config — the state-free design did exactly what it promises. But the blast radius was fleet-wide for a single module's pathology, every in-flight route died outcome-unknown at once, and the kill destroyed the diagnostic trail the supervisor would otherwise have held (the counterfactual noted on PR #10: a cgroup-wide SIGKILL leaves no window to flush anything).

Meanwhile systemctl status ck-subc memory figures are cgroup-wide, so from the outside a 29.5G reading is unattributable — we spent a day of probe-work establishing which process owned the growth, information the supervisor has for free in /proc/<child>/status.

Three asks, in ascending cost — any subset helps, disposition yours

1. Per-module memory telemetry (cheap, additive). The supervisor knows every module's pid. Reading VmRSS/VmSwap/VmData from /proc/<pid>/status on the existing probe cadence and carrying it on SupervisorEntry (surfaced in ck module status) converts "the service is at 29.5G" into "aft is at 27G, everyone else is noise" — the attribution question that cost us a day, answered in one column. Same shape as the restart_count addition in c62c547: current-state, no history claim.

2. Per-module cgroup scopes (isolation). Spawn each supervised module in its own transient scope (systemd-run-style, or a cgroup v2 subtree under the service) so a hoarding module OOMs alone and the supervisor observes a normal abnormal-exit — restart budget applies, crash cause is attributable, innocent modules and the daemon survive, and the #10 stderr tail actually has a surviving reader. This converts fleet outage → single-module restart, which is what supervision is for.

3. Supervisor memory policy (softer than the kernel's). With telemetry from (1), an optional per-module memory_high_bytes in module config: crossing it triggers the existing drain-restart path — a graceful bounce at a threshold of the operator's choosing, instead of a SIGKILL at the kernel's. The aft hoard grows over ~41h; a policy restart at 8G would have been invisible to users and prevented the incident outright.

What we're not asking

No history/series (that class went to observers per #4's ruling), no per-module accounting inside module processes, and (2) may be wrong if scopes fight the launch-nonce/attestation flow — if so, (1)+(3) alone still convert the failure mode from unattributable-fleet-kill to attributed-graceful-restart. Numbers, journal excerpts, and the full incident timeline are on record if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions