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) + } +}