Proposal Details

We propose to add a new Fill method to struct CPUSet, which sets all CPU bits to 1. The implementation can look like this (see CL 698015):

// Fill adds all possible CPU bits to the set s. On Linux, [SchedSetaffinity]
// will silently ignore any invalid CPU bits in [CPUSet] so this is an
// efficient way of resetting the CPU affinity of a process.
func (s *CPUSet) Fill() {
    for i := range s {
        s[i] = ^cpuMask(0)
    }
}

Context

Some programs (such as container runtimes) want to reset their CPU affinity if they are spawned by processes with a particular CPU affinity. Container runtimes didn't really have to deal with this issue until Linux 6.2 when the cpuset cgroup was changed to no longer auto-reset CPU affinity in this case.

A naive approach to resetting your CPU affinity would be to get the number of CPUs by looking at "/proc/stat" or "/sys/devices/system/cpu" (note that runtime.NumCPU() actually returns the CPU affinity of the process at startup time, which isn't useful for this purpose) and then asking for all of those CPUs.

However, sched_setaffinity(2) will silently ignore any CPU bits set in the provided CPUSet if they do not exist or are not enabled in the cpuset cgroup of the process. This means that you can reset your CPU affinity by just setting every CPU bit in CPUSet and passing it to sched_setaffinity(2).

Unfortunately, setting every CPU bit in CPUSet with (*CPUSet).Set() is very inefficient. If it were possible to just memset(0xFF) the CPUSet array, users would be able to reset their CPU affinity even more cheaply. However, Go doesn't have a memset primitive that can be used in that way.

Obvious solutions like setting the array elements of CPUSet to (^0) do not work because CPUSet is an array of a private newtype and so the compiler complains if you try to use a constant like (^0) without a cast (and we cannot use a cast because the type is private):

cannot use ^0 (untyped int constant -1) as
"golang.org/x/sys/unix".cpuMask value in assignment (overflows)

The only real alternative is to do something quite hacky like:

cpuset := unix.CPUSet{}
for i := range cpuset {
    cpuset[i]-- // underflow to 0xFF..FF
}

... which is the solution we use in runc.

It would be much nicer to have a helper that does this memset for us in a less hacky way, since resetting CPU affinity seems like a fairly common operation.

Ref: Linux kernel commit da019032819a ("sched: Enforce user requested affinity")

(Description and implementation by @cyphar)

Comment From: cyphar

Note that this proposal will allow for a fairly useful operation (reset CPU affinity) to be done ~300x faster than the naive approach one would normally do (by using the official Set API for each CPU value) with a 3-line function.

Comment From: aclements

It's unfortunate that CPUSet.Count can return more CPUs than you actually have after Fill, but perhaps there's no good way around that.

Comment From: cyphar

Yeah, though this is also true for using Set for non-existent CPUs.

Comment From: aclements

This proposal has been added to the active column of the proposals project and will now be reviewed at the weekly proposal review meetings. — aclements for the proposal review group

Comment From: aclements

Edited 2025-09-24: Messed up automated post. I meant to just point to the top comment.

Based on the discussion above, this proposal seems like a likely accept. — aclements for the proposal review group

The proposal details are in https://github.com/golang/go/issues/75186#issue-3364235843

Comment From: aclements

No change in consensus, so accepted. 🎉 This issue now tracks the work of implementing the proposal. — aclements for the proposal review group

The proposal details are in https://github.com/golang/go/issues/75186#issue-3364235843

Comment From: gopherbot

Change https://go.dev/cl/698015 mentions this issue: unix: add (*CPUSet).Fill helper to enable all CPUs

Comment From: prattmic

Note that #75566 discusses that the CPUSet type is problematic because it cannot represent more than 1024 CPUs, even though the system call itself can. I don't think that should hold up this proposal, just FYI since it is related.

Comment From: cyphar

Yeah, the API would be the same no matter what the limit is in golang.org/x/sys. In fact, the performance improvement only gets better the larger the maximum possible number gets!

Comment From: kolyshkin

I guess someone will rework (or merely expland) unix.CPUSet once a need arises.

I'm also reusing CPUSet in https://go-review.googlesource.com/c/sys/+/706917.