carlos / platform Public

Merge pull request #342 from carlosframework/reliability

cgroup: bound memory.swap.max with memory.max (zram made the cap escapable)
Paul Campbell pushed by paul@keymail.dev d86a5c16e07404b9ac24d4ceec5fc0ca888e283f parent 6e189a5parent 425ce5d
2 files changed, +10 −0
  • internal/cgroup/cgroup.go +9 −0
  • internal/cgroup/cgroup_test.go +1 −0
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 {