This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / spindle / engines / microvm / cgroup_oom_test.go
3.3 kB 128 lines
1//go:build linux 2 3package microvm 4 5import ( 6 "log/slog" 7 "os" 8 "os/exec" 9 "runtime" 10 "testing" 11 "time" 12 13 cgroups "github.com/containerd/cgroups/v3" 14) 15 16const memhogEnv = "SPINDLE_CGROUP_MEMHOG" 17 18func TestMain(m *testing.M) { 19 if os.Getenv(memhogEnv) == "1" { 20 runMemhogChild() 21 return 22 } 23 os.Exit(m.Run()) 24} 25 26// this will allocate memory in steps until either the cgroup kills the process 27// this is running on, or if the limit is reached. the limit is there so that if 28// the cgroup somehow does not work, we don't kill the host and can observe that 29// failure. 30func runMemhogChild() { 31 var b [1]byte 32 _, _ = os.Stdin.Read(b[:]) 33 34 const chunk = 4 << 20 // 4 MiB 35 const limit = 512 << 20 // safety cap 36 hold := make([][]byte, 0, limit/chunk) 37 for total := 0; total < limit; total += chunk { 38 c := make([]byte, chunk) 39 for i := range c { 40 c[i] = 1 // fault the pages in so they count against memory.current 41 } 42 hold = append(hold, c) 43 time.Sleep(5 * time.Millisecond) 44 } 45 runtime.KeepAlive(hold) 46 os.Exit(0) 47} 48 49// creates a cgroup parent, adds a memory limited child to it, and creates a 50// process that hogs memory and observes if it OOMs or not. 51// 52// run with: 53// 54// SPINDLE_CGROUP_INTEGRATION=1 systemd-run --user --scope -p Delegate=yes \ 55// go test -run TestCgroupOOMEnforcement ./spindle/engines/microvm/ 56func TestCgroupOOMEnforcement(t *testing.T) { 57 if os.Getenv("SPINDLE_CGROUP_INTEGRATION") != "1" { 58 t.Skip("see test doc comment on how to run") 59 } 60 if cgroups.Mode() != cgroups.Unified { 61 t.Skip("requires cgroup v2 unified mode") 62 } 63 64 logger := slog.Default() 65 66 parent, err := initCgroupParent(cgroupParentSelf, 0, logger) 67 if err != nil { 68 t.Skipf("cannot initialize cgroup parent (need cgroup v2 delegation): %v", err) 69 } 70 71 swap := int64(0) // disable swap so the limit forces an OOM promptly 72 handle, err := prepareCgroup(CgroupLimits{ 73 Enabled: true, 74 Parent: parent, 75 Name: "cgtest-oom", 76 MemoryMaxMiB: 64, 77 SwapMaxMiB: &swap, 78 PidsMax: 256, 79 }, logger) 80 if err != nil { 81 t.Skipf("cannot create a memory-limited child cgroup (need the memory controller delegated): %v", err) 82 } 83 if handle == nil { 84 t.Fatal("prepareCgroup returned a nil handle for enabled limits") 85 } 86 t.Cleanup(func() { _ = handle.Close() }) 87 88 cmd := exec.Command(os.Args[0]) 89 cmd.Env = append(os.Environ(), memhogEnv+"=1") 90 stdin, err := cmd.StdinPipe() 91 if err != nil { 92 t.Fatal(err) 93 } 94 if err := cmd.Start(); err != nil { 95 t.Fatal(err) 96 } 97 defer func() { 98 _ = cmd.Process.Kill() 99 _ = cmd.Wait() 100 }() 101 102 if err := handle.AddProcess(cmd.Process.Pid, logger); err != nil { 103 t.Fatalf("add memhog to cgroup: %v", err) 104 } 105 106 // let the child process start allocating memory 107 if _, err := stdin.Write([]byte("g")); err != nil { 108 t.Fatalf("release memhog: %v", err) 109 } 110 _ = stdin.Close() 111 112 waitErr := make(chan error, 1) 113 go func() { waitErr <- cmd.Wait() }() 114 115 select { 116 case err := <-waitErr: 117 if err == nil { 118 t.Fatal("memhog exited cleanly: the cgroup memory limit was not enforced") 119 } 120 t.Logf("memhog died as expected: %v", err) 121 case <-time.After(30 * time.Second): 122 t.Fatal("memhog did not die within 30s, cgroup memory limit not enforced") 123 } 124 125 if !handle.OOMKilled() { 126 t.Fatal("OOMKilled() is false after the memhog was killed, memory.events oom_kill was not observed") 127 } 128}