Findings

1. Duration profile by isolation condition

Green = success, blue = graceful alloc fail, red = OOM/signal kill, amber = thrash timeout.

Suite elapsed

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

Multi worker events

Conditionoom_killoom_group_killSurvivorsParent logs
3×80 MiB under 120 MiB, og=0201/3intact
3×80 MiB under 120 MiB, og=1210empty
2×100 MiB under 96 MiB, og=0200intact
2×100 MiB under 96 MiB, og=1310empty

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)

Memory gates

4. Direct cgroup numbers

TrialPeakEvents (high / max / oom / oom_kill / group)Outcome
max=96M96.0 MiB0 / 38 / 1 / 1 / 0SIGKILL
max=96M + og=196.0 MiB0 / 38 / 1 / 2 / 1SIGKILL + group
high=64M, max unlimited~72.7 MiB1040 / 0 / 0 / 0 / 050 s timeout at 64 MiB
high=96M, max=128M~109.7 MiB1790 / 0 / 0 / 0 / 060 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

  1. Put only the dangerous tool tree in a cgroup.
  2. Set memory.swap.max=0, memory.max=<cap>, optionally memory.high around 0.8 times max.
  3. For multi-process tools that must restart cleanly: memory.oom.group=1 on the worker subtree; keep the supervisor outside.
  4. Do not rely on systemd-run -p MemoryMax=max or MemoryOOMGroup= — they are rejected on systemd 257 / EL10.