From dc09e2816b2e239d8c5ab987bbbd8b78ae119639 Mon Sep 17 00:00:00 2001 From: Andrew Halaney Date: Mon, 13 Jul 2026 13:48:42 -0500 Subject: [PATCH] getallpids: Ignore removed cgroups Determining what pids belong to a cgroup hierarchy is a racy task. At any moment you could have something like: 1. Start recursively reading cgroup hiearchy 2. Delete a leaf of the hierarchy 3. Prior recursive read fails on the now deleted leaf The pid could vanish after you determine the hierachy, it could get moved, etc. New cgroups could also be added. Its hard to get right with the current cgroupv2 api. Let's just loosen up a bit here, if we try to read info about a cgroup and the things gone just continue onwards. We know that the state has changed since we started reading, but that's already a possible situation (i.e. you've _added_ a new cgroup, that wouldn't cause an error but you'd miss the pid in it). Link: https://github.com/containerd/containerd/issues/13784 Signed-off-by: Andrew Halaney --- getallpids.go | 17 +++++++++++++++++ getallpids_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/getallpids.go b/getallpids.go index 1355a51..ecd60a4 100644 --- a/getallpids.go +++ b/getallpids.go @@ -1,8 +1,12 @@ package cgroups import ( + "errors" "io/fs" + "os" "path/filepath" + + "golang.org/x/sys/unix" ) // GetAllPids returns all pids from the cgroup identified by path, and all its @@ -11,6 +15,11 @@ func GetAllPids(path string) ([]int, error) { var pids []int err := filepath.WalkDir(path, func(p string, d fs.DirEntry, iErr error) error { if iErr != nil { + // A descendant cgroup can be removed while we walk, ignore + // any such error unless it's on the root (path) cgroup here + if p != path && ignoreCgroupRemoved(iErr) { + return nil + } return iErr } if !d.IsDir() { @@ -18,6 +27,9 @@ func GetAllPids(path string) ([]int, error) { } cPids, err := readProcsFile(p) if err != nil { + if p != path && ignoreCgroupRemoved(err) { + return nil + } return err } pids = append(pids, cPids...) @@ -25,3 +37,8 @@ func GetAllPids(path string) ([]int, error) { }) return pids, err } + +// ignoreCgroupRemoved reports whether err indicates the cgroup was removed. +func ignoreCgroupRemoved(err error) bool { + return os.IsNotExist(err) || errors.Is(err, unix.ENODEV) +} diff --git a/getallpids_test.go b/getallpids_test.go index 125fa0a..a3a2164 100644 --- a/getallpids_test.go +++ b/getallpids_test.go @@ -1,6 +1,9 @@ package cgroups import ( + "os" + "path/filepath" + "slices" "testing" ) @@ -15,3 +18,34 @@ func BenchmarkGetAllPids(b *testing.B) { } b.Logf("iter: %d, total: %d", b.N, total) } + +func TestGetAllPidsIgnoresVanishedSubcgroup(t *testing.T) { + TestMode = true + defer func() { TestMode = false }() + + root := t.TempDir() + if err := WriteFile(root, "cgroup.procs", "1\n"); err != nil { + t.Fatal(err) + } + child := filepath.Join(root, "child") + if err := os.Mkdir(child, 0o755); err != nil { + t.Fatal(err) + } + if err := WriteFile(child, "cgroup.procs", "2\n3\n"); err != nil { + t.Fatal(err) + } + + // Pretend "vanishing" cgroup, directory exists but not the file to read + if err := os.Mkdir(filepath.Join(root, "gone"), 0o755); err != nil { + t.Fatal(err) + } + + pids, err := GetAllPids(root) + if err != nil { + t.Fatalf("GetAllPids errored on a vanished child cgroup: %v", err) + } + slices.Sort(pids) + if want := []int{1, 2, 3}; !slices.Equal(pids, want) { + t.Errorf("GetAllPids() = %v, want %v", pids, want) + } +}