| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 |
- //go:build linux && !android && !e2e_testing
- package util
- import (
- "errors"
- "fmt"
- "os"
- "strconv"
- "unsafe"
- "golang.org/x/sys/unix"
- )
- // AvoidCPUs removes cpus from the affinity of every thread of the process, for use at startup. New threads inherit
- // their parent's mask, so it repeats until a pass finds nothing left to change; a thread that exits meanwhile is
- // skipped. Every mask is checked before any is changed. With apply false it only validates, for configuration tests.
- // This affects the entire process, including other users of an embedded Nebula.
- func AvoidCPUs(cpus []int, apply bool) error {
- const capacity = int(unsafe.Sizeof(unix.CPUSet{})) * 8
- for _, cpu := range cpus {
- if cpu < 0 || cpu >= capacity {
- return fmt.Errorf("CPU %d is out of range", cpu)
- }
- }
- if len(cpus) == 0 {
- return nil
- }
- for {
- masks, err := avoidingMasks(cpus)
- if err != nil || !apply || len(masks) == 0 {
- return err
- }
- for tid, mask := range masks {
- if err := unix.SchedSetaffinity(tid, &mask); err != nil && !errors.Is(err, unix.ESRCH) {
- return fmt.Errorf("set thread %d affinity: %w", tid, err)
- }
- }
- }
- }
- // avoidingMasks returns the mask without cpus of every thread whose mask has any of them.
- func avoidingMasks(cpus []int) (map[int]unix.CPUSet, error) {
- entries, err := os.ReadDir("/proc/self/task")
- if err != nil {
- return nil, err
- }
- masks := make(map[int]unix.CPUSet, len(entries))
- for _, entry := range entries {
- tid, err := strconv.Atoi(entry.Name())
- if err != nil {
- return nil, err
- }
- var mask unix.CPUSet
- err = unix.SchedGetaffinity(tid, &mask)
- if errors.Is(err, unix.ESRCH) {
- continue
- }
- if err != nil {
- return nil, fmt.Errorf("read thread %d affinity: %w", tid, err)
- }
- next := mask
- for _, cpu := range cpus {
- next.Clear(cpu)
- }
- if next.Count() == 0 {
- return nil, fmt.Errorf("avoid_cpus leaves thread %d with no allowed CPU", tid)
- }
- if next != mask {
- masks[tid] = next
- }
- }
- return masks, nil
- }
|