| index 58f5491df..b6a38a8dc 100644 |
| --- a/internal/cgroup/cgroup.go |
| +++ b/internal/cgroup/cgroup.go |
| @@ -310,6 +310,15 @@ func (m *Manager) applyLimit(host, dir string, limit Limit) { |
| if err := os.WriteFile(filepath.Join(dir, "memory.high"), []byte(strconv.FormatInt(highOf(limit.MemoryMax), 10)), 0o644); err != nil { |
| m.logf("cgroup: %s: memory.high: %v — child is capped but not throttled first", host, err) |
| } |
| + // Swap is bounded too, or the cap isn't one: with zram on the box a |
| + // cgroup at memory.max simply pushes its anonymous pages into |
| + // compressed swap and carries on — a 200 MB allocator under a 64M cap |
| + // "survived" on the flagship, live, until this line. Same number as |
| + // max: a tenant may spill up to its cap into swap (the slow-then-dead |
| + // gradient), then the kill is real. |
| + if err := os.WriteFile(filepath.Join(dir, "memory.swap.max"), []byte(strconv.FormatInt(limit.MemoryMax, 10)), 0o644); err != nil { |
| + m.logf("cgroup: %s: memory.swap.max: %v — the cap can be exceeded into swap", host, err) |
| + } |
| if err := os.WriteFile(filepath.Join(dir, "memory.oom.group"), []byte("1"), 0o644); err != nil { |
| m.logf("cgroup: %s: memory.oom.group: %v — an OOM kill here takes one process, not the family", host, err) |
| } |