blob: 99bc9f1dbb350a50dcc4559973a2ff53a460ac05 [file] [log] [blame]
// 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 main
import (
"fmt"
"os"
"runtime"
"strconv"
"time"
)
func init() {
register("PrintGOMAXPROCS", PrintGOMAXPROCS)
register("SetLimitThenDefaultGOMAXPROCS", SetLimitThenDefaultGOMAXPROCS)
register("UpdateGOMAXPROCS", UpdateGOMAXPROCS)
register("DontUpdateGOMAXPROCS", DontUpdateGOMAXPROCS)
}
func PrintGOMAXPROCS() {
println(runtime.GOMAXPROCS(0))
}
func mustSetCPUMax(path string, quota int64) {
q := "max"
if quota >= 0 {
q = strconv.FormatInt(quota, 10)
}
buf := fmt.Sprintf("%s 100000", q)
if err := os.WriteFile(path, []byte(buf), 0); err != nil {
panic(fmt.Sprintf("error setting cpu.max: %v", err))
}
}
func mustParseInt64(s string) int64 {
v, err := strconv.ParseInt(s, 10, 64)
if err != nil {
panic(err)
}
return v
}
// Inputs:
// GO_TEST_CPU_MAX_PATH: Path to cgroup v2 cpu.max file.
// GO_TEST_CPU_MAX_QUOTA: CPU quota to set.
func SetLimitThenDefaultGOMAXPROCS() {
path := os.Getenv("GO_TEST_CPU_MAX_PATH")
quota := mustParseInt64(os.Getenv("GO_TEST_CPU_MAX_QUOTA"))
mustSetCPUMax(path, quota)
runtime.SetDefaultGOMAXPROCS()
println(runtime.GOMAXPROCS(0))
}
// Wait for GOMAXPROCS to change from from to to. Times out after 10s.
func waitForMaxProcsChange(from, to int) {
start := time.Now()
for {
if time.Since(start) > 10*time.Second {
panic("no update for >10s")
}
procs := runtime.GOMAXPROCS(0)
println("GOMAXPROCS:", procs)
if procs == to {
return
}
if procs != from {
panic(fmt.Sprintf("GOMAXPROCS change got %d want %d", procs, to))
}
time.Sleep(100*time.Millisecond)
}
}
// Make sure that GOMAXPROCS does not change from curr.
//
// It is impossible to assert that it never changes, so this just makes sure it
// stays for 5s.
func mustNotChangeMaxProcs(curr int) {
start := time.Now()
for {
if time.Since(start) > 5*time.Second {
return
}
procs := runtime.GOMAXPROCS(0)
println("GOMAXPROCS:", procs)
if procs != curr {
panic(fmt.Sprintf("GOMAXPROCS change got %d want %d", procs, curr))
}
time.Sleep(100*time.Millisecond)
}
}
// Inputs:
// GO_TEST_CPU_MAX_PATH: Path to cgroup v2 cpu.max file.
func UpdateGOMAXPROCS() {
// We start with no limit.
ncpu := runtime.NumCPU()
procs := runtime.GOMAXPROCS(0)
println("GOMAXPROCS:", procs)
if procs != ncpu {
panic(fmt.Sprintf("GOMAXPROCS got %d want %d", procs, ncpu))
}
path := os.Getenv("GO_TEST_CPU_MAX_PATH")
// Drop down to 3 CPU.
mustSetCPUMax(path, 300000)
waitForMaxProcsChange(ncpu, 3)
// Drop even further. Now we hit the minimum GOMAXPROCS=2.
mustSetCPUMax(path, 100000)
waitForMaxProcsChange(3, 2)
// Increase back up.
mustSetCPUMax(path, 300000)
waitForMaxProcsChange(2, 3)
// Remove limit entirely.
mustSetCPUMax(path, -1)
waitForMaxProcsChange(3, ncpu)
// Setting GOMAXPROCS explicitly disables updates.
runtime.GOMAXPROCS(3)
mustSetCPUMax(path, 200000)
mustNotChangeMaxProcs(3)
// Re-enable updates. Change is immediately visible.
runtime.SetDefaultGOMAXPROCS()
procs = runtime.GOMAXPROCS(0)
println("GOMAXPROCS:", procs)
if procs != 2 {
panic(fmt.Sprintf("GOMAXPROCS got %d want %d", procs, 2))
}
// Setting GOMAXPROCS to itself also disables updates, despite not
// changing the value itself.
runtime.GOMAXPROCS(runtime.GOMAXPROCS(0))
mustSetCPUMax(path, 300000)
mustNotChangeMaxProcs(2)
println("OK")
}
// Inputs:
// GO_TEST_CPU_MAX_PATH: Path to cgroup v2 cpu.max file.
func DontUpdateGOMAXPROCS() {
// The caller has disabled updates. Make sure they don't happen.
curr := runtime.GOMAXPROCS(0)
println("GOMAXPROCS:", curr)
path := os.Getenv("GO_TEST_CPU_MAX_PATH")
mustSetCPUMax(path, 300000)
mustNotChangeMaxProcs(curr)
println("OK")
}