cgroup: bound memory.swap.max alongside memory.max — with zram on the box a capped tenant just swapped past its cap (live-proven on the flagship)
2 files changed,
+10
−0
internal/cgroup/cgroup.go+9 −0internal/cgroup/cgroup_test.go+1 −0
CI passed — run details
diff --git a/internal/cgroup/cgroup.go b/internal/cgroup/cgroup.go| 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) |
| } |
diff --git a/internal/cgroup/cgroup_test.go b/internal/cgroup/cgroup_test.go| index b7858b343..a2e26f9f6 100644 |
| --- a/internal/cgroup/cgroup_test.go |
| +++ b/internal/cgroup/cgroup_test.go |
| @@ -287,6 +287,7 @@ func TestStartWritesMemoryLimit(t *testing.T) { |
| "memory.max": "1073741824", |
| "memory.high": "966367638", // max/10*9, integer arithmetic |
| "memory.oom.group": "1", |
| + "memory.swap.max": "1073741824", // = max: the cap is total, not RAM-only |
| } { |
| got, err := os.ReadFile(filepath.Join(dir, file)) |
| if err != nil { |