Monorepo for Tangled tangled.org
2

Configure Feed

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

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