Findings
1. Duration profile by isolation condition
Green = success, blue = graceful alloc fail, red = OOM/signal kill, amber = thrash timeout.
Interpretation. Baseline and parent-alive paths finish sub-second. prlimit fails fast with MemoryError. MemoryMax kills in under a second. Tiered MemoryHigh plus MemoryMax via systemd sat for the full 90 s timeout at the high boundary — reclaim pressure without a clean kill.
2. Multi-worker selective kill vs group wipe
| Condition | oom_kill | oom_group_kill | Survivors | Parent logs |
|---|---|---|---|---|
| 3×80 MiB under 120 MiB, og=0 | 2 | 0 | 1/3 | intact |
| 3×80 MiB under 120 MiB, og=1 | 2 | 1 | 0 | empty |
| 2×100 MiB under 96 MiB, og=0 | 2 | 0 | 0 | intact |
| 2×100 MiB under 96 MiB, og=1 | 3 | 1 | 0 | empty |
With memory.oom.group=0, the killer snipes high-score tasks until charges fit — a lucky worker can finish. With =1, the cgroup is an atomic failure domain, including the coordinator that entered it to fork workers.
3. Memory gates (kernel model)
4. Direct cgroup numbers
| Trial | Peak | Events (high / max / oom / oom_kill / group) | Outcome |
|---|---|---|---|
| max=96M | 96.0 MiB | 0 / 38 / 1 / 1 / 0 | SIGKILL |
| max=96M + og=1 | 96.0 MiB | 0 / 38 / 1 / 2 / 1 | SIGKILL + group |
| high=64M, max unlimited | ~72.7 MiB | 1040 / 0 / 0 / 0 / 0 | 50 s timeout at 64 MiB |
| high=96M, max=128M | ~109.7 MiB | 1790 / 0 / 0 / 0 / 0 | 60 s timeout at 96 MiB |
5. Collateral
Uncaged parent + MemoryMax=80M child: child rc=-9, parent PARENT_ALIVE in 3/3 trials. Memcg OOM does not jump the cgroup boundary — so a parent that buffers child output outside the cage remains a host hazard.
Recipe
- Put only the dangerous tool tree in a cgroup.
- Set
memory.swap.max=0,memory.max=<cap>, optionallymemory.higharound 0.8 times max. - For multi-process tools that must restart cleanly:
memory.oom.group=1on the worker subtree; keep the supervisor outside. - Do not rely on
systemd-run -p MemoryMax=maxorMemoryOOMGroup=— they are rejected on systemd 257 / EL10.