| // Copyright 2025 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package runtime | |
| import ( | |
| "internal/runtime/cgroup" | |
| ) | |
| // cgroup-aware GOMAXPROCS default | |
| // | |
| // At startup (defaultGOMAXPROCSInit), we read /proc/self/cgroup and /proc/self/mountinfo | |
| // to find our current CPU cgroup and open its limit file(s), which remain open | |
| // for the entire process lifetime. We periodically read the current limit by | |
| // rereading the limit file(s) from the beginning. | |
| // | |
| // This makes reading updated limits simple, but has a few downsides: | |
| // | |
| // 1. We only read the limit from the leaf cgroup that actually contains this | |
| // process. But a parent cgroup may have a tighter limit. That tighter limit | |
| // would be our effective limit. That said, container runtimes tend to hide | |
| // parent cgroups from the container anyway. | |
| // | |
| // 2. If the process is migrated to another cgroup while it is running it will | |
| // not notice, as we only check which cgroup we are in once at startup. | |
| var ( | |
| // We can't allocate during early initialization when we need to find | |
| // the cgroup. Simply use a fixed global as a scratch parsing buffer. | |
| cgroupScratch [cgroup.ScratchSize]byte | |
| cgroupOK bool | |
| cgroupCPU cgroup.CPU | |
| // defaultGOMAXPROCSInit runs before internal/godebug init, so we can't | |
| // directly update the GODEBUG counter. Store the result until after | |
| // init runs. | |
| containermaxprocsNonDefault bool | |
| containermaxprocs = &godebugInc{name: "containermaxprocs"} | |
| ) | |
| // Prepare for defaultGOMAXPROCS. | |
| // | |
| // Must run after parsedebugvars. | |
| func defaultGOMAXPROCSInit() { | |
| c, err := cgroup.OpenCPU(cgroupScratch[:]) | |
| if err != nil { | |
| // Likely cgroup.ErrNoCgroup. | |
| return | |
| } | |
| if debug.containermaxprocs > 0 { | |
| // Normal operation. | |
| cgroupCPU = c | |
| cgroupOK = true | |
| return | |
| } | |
| // cgroup-aware GOMAXPROCS is disabled. We still check the cgroup once | |
| // at startup to see if enabling the GODEBUG would result in a | |
| // different default GOMAXPROCS. If so, we increment runtime/metrics | |
| // /godebug/non-default-behavior/cgroupgomaxprocs:events. | |
| procs := getCPUCount() | |
| cgroupProcs := adjustCgroupGOMAXPROCS(procs, c) | |
| if procs != cgroupProcs { | |
| containermaxprocsNonDefault = true | |
| } | |
| // Don't need the cgroup for remaining execution. | |
| c.Close() | |
| } | |
| // defaultGOMAXPROCSUpdateGODEBUG updates the internal/godebug counter for | |
| // container GOMAXPROCS, once internal/godebug is initialized. | |
| func defaultGOMAXPROCSUpdateGODEBUG() { | |
| if containermaxprocsNonDefault { | |
| containermaxprocs.IncNonDefault() | |
| } | |
| } | |
| // Return the default value for GOMAXPROCS when it has not been set explicitly. | |
| // | |
| // ncpu is the optional precomputed value of getCPUCount. If passed as 0, | |
| // defaultGOMAXPROCS will call getCPUCount. | |
| func defaultGOMAXPROCS(ncpu int32) int32 { | |
| // GOMAXPROCS is the minimum of: | |
| // | |
| // 1. Total number of logical CPUs available from sched_getaffinity. | |
| // | |
| // 2. The average CPU cgroup throughput limit (average throughput = | |
| // quota/period). A limit less than 2 is rounded up to 2, and any | |
| // fractional component is rounded up. | |
| // | |
| // TODO: add rationale. | |
| procs := ncpu | |
| if procs <= 0 { | |
| procs = getCPUCount() | |
| } | |
| if !cgroupOK { | |
| // No cgroup, or disabled by debug.containermaxprocs. | |
| return procs | |
| } | |
| return adjustCgroupGOMAXPROCS(procs, cgroupCPU) | |
| } | |
| // Lower procs as necessary for the current cgroup CPU limit. | |
| func adjustCgroupGOMAXPROCS(procs int32, cpu cgroup.CPU) int32 { | |
| limit, ok, err := cgroup.ReadCPULimit(cpu) | |
| if err == nil && ok { | |
| limit = ceil(limit) | |
| limit = max(limit, 2) | |
| if int32(limit) < procs { | |
| procs = int32(limit) | |
| } | |
| } | |
| return procs | |
| } |