// 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 (
	
)

// 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() {
	,  := cgroup.OpenCPU(cgroupScratch[:])
	if  != nil {
		// Likely cgroup.ErrNoCgroup.
		return
	}

	if debug.containermaxprocs > 0 {
		// Normal operation.
		cgroupCPU = 
		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.
	 := getCPUCount()
	 := adjustCgroupGOMAXPROCS(, )
	if  !=  {
		containermaxprocsNonDefault = true
	}

	// Don't need the cgroup for remaining execution.
	.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( 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.

	 := 
	if  <= 0 {
		 = getCPUCount()
	}
	if !cgroupOK {
		// No cgroup, or disabled by debug.containermaxprocs.
		return 
	}

	return adjustCgroupGOMAXPROCS(, cgroupCPU)
}

// Lower procs as necessary for the current cgroup CPU limit.
func adjustCgroupGOMAXPROCS( int32,  cgroup.CPU) int32 {
	, ,  := cgroup.ReadCPULimit()
	if  == nil &&  {
		 = ceil()
		 = max(, 2)
		if int32() <  {
			 = int32()
		}
	}
	return 
}