// 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) 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") }