Source file src/runtime/cgroup_linux.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package runtime
     6  
     7  import (
     8  	"internal/runtime/cgroup"
     9  )
    10  
    11  // cgroup-aware GOMAXPROCS default
    12  //
    13  // At startup (defaultGOMAXPROCSInit), we read /proc/self/cgroup and /proc/self/mountinfo
    14  // to find our current CPU cgroup and open its limit file(s), which remain open
    15  // for the entire process lifetime. We periodically read the current limit by
    16  // rereading the limit file(s) from the beginning.
    17  //
    18  // This makes reading updated limits simple, but has a few downsides:
    19  //
    20  // 1. We only read the limit from the leaf cgroup that actually contains this
    21  // process. But a parent cgroup may have a tighter limit. That tighter limit
    22  // would be our effective limit. That said, container runtimes tend to hide
    23  // parent cgroups from the container anyway.
    24  //
    25  // 2. If the process is migrated to another cgroup while it is running it will
    26  // not notice, as we only check which cgroup we are in once at startup.
    27  var (
    28  	// We can't allocate during early initialization when we need to find
    29  	// the cgroup. Simply use a fixed global as a scratch parsing buffer.
    30  	cgroupScratch [cgroup.ScratchSize]byte
    31  
    32  	cgroupOK  bool
    33  	cgroupCPU cgroup.CPU
    34  
    35  	// defaultGOMAXPROCSInit runs before internal/godebug init, so we can't
    36  	// directly update the GODEBUG counter. Store the result until after
    37  	// init runs.
    38  	containermaxprocsNonDefault bool
    39  	containermaxprocs           = &godebugInc{name: "containermaxprocs"}
    40  )
    41  
    42  // Prepare for defaultGOMAXPROCS.
    43  //
    44  // Must run after parsedebugvars.
    45  func defaultGOMAXPROCSInit() {
    46  	c, err := cgroup.OpenCPU(cgroupScratch[:])
    47  	if err != nil {
    48  		// Likely cgroup.ErrNoCgroup.
    49  		return
    50  	}
    51  
    52  	if debug.containermaxprocs > 0 {
    53  		// Normal operation.
    54  		cgroupCPU = c
    55  		cgroupOK = true
    56  		return
    57  	}
    58  
    59  	// cgroup-aware GOMAXPROCS is disabled. We still check the cgroup once
    60  	// at startup to see if enabling the GODEBUG would result in a
    61  	// different default GOMAXPROCS. If so, we increment runtime/metrics
    62  	// /godebug/non-default-behavior/cgroupgomaxprocs:events.
    63  	procs := getCPUCount()
    64  	cgroupProcs := adjustCgroupGOMAXPROCS(procs, c)
    65  	if procs != cgroupProcs {
    66  		containermaxprocsNonDefault = true
    67  	}
    68  
    69  	// Don't need the cgroup for remaining execution.
    70  	c.Close()
    71  }
    72  
    73  // defaultGOMAXPROCSUpdateGODEBUG updates the internal/godebug counter for
    74  // container GOMAXPROCS, once internal/godebug is initialized.
    75  func defaultGOMAXPROCSUpdateGODEBUG() {
    76  	if containermaxprocsNonDefault {
    77  		containermaxprocs.IncNonDefault()
    78  	}
    79  }
    80  
    81  // Return the default value for GOMAXPROCS when it has not been set explicitly.
    82  //
    83  // ncpu is the optional precomputed value of getCPUCount. If passed as 0,
    84  // defaultGOMAXPROCS will call getCPUCount.
    85  func defaultGOMAXPROCS(ncpu int32) int32 {
    86  	// GOMAXPROCS is the minimum of:
    87  	//
    88  	// 1. Total number of logical CPUs available from sched_getaffinity.
    89  	//
    90  	// 2. The average CPU cgroup throughput limit (average throughput =
    91  	// quota/period). A limit less than 2 is rounded up to 2, and any
    92  	// fractional component is rounded up.
    93  	//
    94  	// TODO: add rationale.
    95  
    96  	procs := ncpu
    97  	if procs <= 0 {
    98  		procs = getCPUCount()
    99  	}
   100  	if !cgroupOK {
   101  		// No cgroup, or disabled by debug.containermaxprocs.
   102  		return procs
   103  	}
   104  
   105  	return adjustCgroupGOMAXPROCS(procs, cgroupCPU)
   106  }
   107  
   108  // Lower procs as necessary for the current cgroup CPU limit.
   109  func adjustCgroupGOMAXPROCS(procs int32, cpu cgroup.CPU) int32 {
   110  	limit, ok, err := cgroup.ReadCPULimit(cpu)
   111  	if err == nil && ok {
   112  		limit = ceil(limit)
   113  		limit = max(limit, 2)
   114  		if int32(limit) < procs {
   115  			procs = int32(limit)
   116  		}
   117  	}
   118  	return procs
   119  }
   120  

View as plain text