unix: add CPUSetDynamic for systems with more than 1024 CPUs

The existing CPUSet type is a fixed-size array limited to 1024 CPUs,
which makes it problematic to use for large systems (such as Google's
X4 instances with 1440 and 1920 vCPUs), see e.g.
https://github.com/opencontainers/runc/issues/5023.

Introduce CPUSetDynamic type and NewCPUSet constructor to support large
systems. The bit-managing routines (set/clear/isset/fill/count) are
separated and reused.

Add variants of SchedGetaffinity, SchedSetaffinity and SetMemPolicy
that accept the new type.

Amend the documentation for CPUSet.

Amend the existing TestSchedSetaffinity to:
 - test set.Fill;
 - use t.Cleanup to restore the affinity.

Add tests for new functionality (mostly a copy of existing tests).

This is an alternative to CL 727540 / CL 727541.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Change-Id: I51bba0305b8dfa7a88a4e7fb8758d73f798574f1
Reviewed-on: https://go-review.googlesource.com/c/sys/+/735380
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Kir Kolyshkin
2026-04-08 16:32:30 -07:00
committed by Kirill Kolyshkin
co-authored by Claude Sonnet 4.5
parent f33a730cd0
commit 690c91f6ec
4 changed files with 216 additions and 23 deletions
+96 -3
View File
@@ -19,6 +19,7 @@ import (
"path/filepath"
"runtime"
"runtime/debug"
"slices"
"strconv"
"strings"
"syscall"
@@ -512,7 +513,12 @@ func TestPselectWithSigmask(t *testing.T) {
}
func TestSchedSetaffinity(t *testing.T) {
const maxcpus = 1024 // _CPU_SETSIZE
var newMask unix.CPUSet
newMask.Fill()
if count := newMask.Count(); count != maxcpus {
t.Errorf("Fill: got %d CPUs, want %d", count, maxcpus)
}
newMask.Zero()
if newMask.Count() != 0 {
t.Errorf("CpuZero: didn't zero CPU set: %v", newMask)
@@ -566,6 +572,14 @@ func TestSchedSetaffinity(t *testing.T) {
}
}
t.Cleanup(func() {
// Restore old mask so it doesn't affect successive tests.
err = unix.SchedSetaffinity(0, &oldMask)
if err != nil {
t.Fatalf("SchedSetaffinity: %v", err)
}
})
err = unix.SchedSetaffinity(0, &newMask)
if err != nil {
t.Fatalf("SchedSetaffinity: %v", err)
@@ -580,11 +594,90 @@ func TestSchedSetaffinity(t *testing.T) {
if gotMask != newMask {
t.Errorf("SchedSetaffinity: returned affinity mask does not match set affinity mask")
}
}
// Restore old mask so it doesn't affect successive tests
err = unix.SchedSetaffinity(0, &oldMask)
func TestSchedSetaffinityDynamic(t *testing.T) {
const maxcpus = 4096
newMask := unix.NewCPUSet(maxcpus)
newMask.Fill()
if count := newMask.Count(); count != maxcpus {
t.Errorf("Fill: got %d CPUs, want %d", count, maxcpus)
}
newMask.Zero()
if newMask.Count() != 0 {
t.Errorf("Zero: didn't zero CPU set: %v", newMask)
}
cpu := 1
newMask.Set(cpu)
if newMask.Count() != 1 || !newMask.IsSet(cpu) {
t.Errorf("Set: didn't set CPU %d in set: %v", cpu, newMask)
}
cpu = 5
newMask.Set(cpu)
if newMask.Count() != 2 || !newMask.IsSet(cpu) {
t.Errorf("Set: didn't set CPU %d in set: %v", cpu, newMask)
}
newMask.Clear(cpu)
if newMask.Count() != 1 || newMask.IsSet(cpu) {
t.Errorf("Clear: didn't clear CPU %d in set: %v", cpu, newMask)
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
oldMask := unix.NewCPUSet(maxcpus)
err := unix.SchedGetaffinityDynamic(0, oldMask)
if err != nil {
t.Fatalf("SchedSetaffinity: %v", err)
t.Fatalf("SchedGetaffinityDynamic: %v", err)
}
if runtime.NumCPU() < 2 {
t.Skip("skipping setaffinity tests on single CPU system")
}
if runtime.GOOS == "android" {
t.Skip("skipping setaffinity tests on android")
}
// On a system like ppc64x where some cores can be disabled using ppc64_cpu,
// setaffinity should only be called with enabled cores. The valid cores
// are found from the oldMask, but if none are found then the setaffinity
// tests are skipped. Issue #27875.
cpu = 1
if !oldMask.IsSet(cpu) {
newMask.Zero()
for i := range len(oldMask) {
if oldMask.IsSet(i) {
newMask.Set(i)
break
}
}
if newMask.Count() == 0 {
t.Skip("skipping setaffinity tests if CPU not available")
}
}
t.Cleanup(func() {
// Restore old mask so it doesn't affect successive tests.
err = unix.SchedSetaffinityDynamic(0, oldMask)
if err != nil {
t.Fatalf("SchedSetaffinityDynamic: %v", err)
}
})
err = unix.SchedSetaffinityDynamic(0, newMask)
if err != nil {
t.Fatalf("SchedSetaffinityDynamic: %v", err)
}
gotMask := unix.NewCPUSet(maxcpus)
err = unix.SchedGetaffinityDynamic(0, gotMask)
if err != nil {
t.Fatalf("SchedGetaffinityDynamic: %v", err)
}
if !slices.Equal(gotMask, newMask) {
t.Errorf("SchedSetaffinityDynamic: returned affinity mask does not match set affinity mask (%+v != %+v", gotMask, newMask)
}
}