Repository: opencontainers/runc
File: libcontainer/process_linux.go (Go)

Code before:
	// kcommit da019032819a ("sched: Enforce user requested affinity") changed
	// this behaviour in Linux 6.2.
	//
	// Parsing cpuset.cpus.effective is quite inefficient (and looking at
	// things like /proc/stat would be wrong for most nested containers), but
	// luckily sched_setaffinity(2) will implicitly:
	//
	//  * Clamp the cpumask so that it matches the current number of CPUs on
	//    the system.
	//  * Mask out any CPUs that are not a member of the target task's
	//    configured cgroup cpuset.
	//
	// So we can just pass a very large array of set cpumask bits and the
	// kernel will silently convert that to the correct value very cheaply.

	// Ideally, we would just set the array to 0xFF...FF. Unfortunately, the
	// size depends on the architecture. It is also a private newtype, so we
	// can't use (^0) or generics since those require us to be able to name the
	// type. However, we can just underflow the zero value instead.
	// TODO: Once <https://golang.org/cl/698015> is merged, switch to that.
	cpuset := unix.CPUSet{}
	for i := range cpuset {
		cpuset[i]-- // underflow to 0xFF..FF
	}
	if err := unix.SchedSetaffinity(pid, &cpuset); err != nil {
		return fmt.Errorf("reset cpu affinity: sched_setaffinity (all %d cpus): %w", cpuset.Count(), err)
	}
	return nil
}

// Starts setns process with specified initial CPU affinity.
func (p *setnsProcess) startWithCPUAffinity() error {
	aff := p.config.CPUAffinity
	if aff == nil || aff.Initial == nil {
		return p.cmd.Start()
	}
	errCh := make(chan error)
	defer close(errCh)

	// Use a goroutine to dedicate an OS thread.
	go func() {
		runtime.LockOSThread()
		// Command inherits the CPU affinity.
		if err := unix.SchedSetaffinity(unix.Gettid(), aff.Initial); err != nil {
			errCh <- fmt.Errorf("error setting initial CPU affinity: %w", err)
			return
		}

		errCh <- p.cmd.Start()
		// Deliberately omit runtime.UnlockOSThread here.
		// https://pkg.go.dev/runtime#LockOSThread says:

Review comment (suggestion):
Perhaps we shouldn't return an error; simply logging it for the user might be sufficient. This is because the `SYS_SCHED_SETAFFINITY` system call might be blocked within some certain dind containers.

Code after:
	// rather than some more restrictive affinity we were spawned in (such as
	// one that may have been inherited from systemd). The cpuset cgroup used
	// to reconfigure the cpumask automatically for joining processes, but
	// kcommit da019032819a ("sched: Enforce user requested affinity") changed
	// this behaviour in Linux 6.2.
	//
	// Parsing cpuset.cpus.effective is quite inefficient (and looking at
	// things like /proc/stat would be wrong for most nested containers), but
	// luckily sched_setaffinity(2) will implicitly:
	//
	//  * Clamp the cpumask so that it matches the current number of CPUs on
	//    the system.
	//  * Mask out any CPUs that are not a member of the target task's
	//    configured cgroup cpuset.
	//
	// So we can just pass a very large array of set cpumask bits and the
	// kernel will silently convert that to the correct value very cheaply.

	// Ideally, we would just set the array to 0xFF...FF. Unfortunately, the
	// size depends on the architecture. It is also a private newtype, so we
	// can't use (^0) or generics since those require us to be able to name the
	// type. However, we can just underflow the zero value instead.
	// TODO: Once <https://golang.org/cl/698015> is merged, switch to that.
	cpuset := unix.CPUSet{}
	for i := range cpuset {
		cpuset[i]-- // underflow to 0xFF..FF
	}
	if err := unix.SchedSetaffinity(pid, &cpuset); err != nil {
		logrus.WithError(
			os.NewSyscallError("sched_setaffinity", err),
		).Warnf("resetting the CPU affinity of pid %d failed -- the container process may inherit runc's CPU affinity", pid)
	}
}

// Starts setns process with specified initial CPU affinity.
func (p *setnsProcess) startWithCPUAffinity() error {
	aff := p.config.CPUAffinity
	if aff == nil || aff.Initial == nil {
		return p.cmd.Start()
	}
	errCh := make(chan error)
	defer close(errCh)

	// Use a goroutine to dedicate an OS thread.
	go func() {
		runtime.LockOSThread()
		// Command inherits the CPU affinity.
		if err := unix.SchedSetaffinity(unix.Gettid(), aff.Initial); err != nil {
			errCh <- fmt.Errorf("error setting initial CPU affinity: %w", err)
			return
		}