// 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 ( "runtime" ) func init() { register("mexitSTW", mexitSTW) } // Stress test for pp.oldm pointing to an exited M. // // If pp.oldm points to an exited M it should be ignored and another M used // instead. To stress: // // 1. Start and exit many threads (thus setting oldm on some P). // 2. Meanwhile, frequently stop the world. // // If procresize incorrect attempts to assign a P to an exited M, likely // failure modes are: // // 1. Crash in startTheWorldWithSema attempting to access the M, if it is nil. // // 2. Memory corruption elsewhere after startTheWorldWithSema writes to the M, // if it is not nil, but is freed and reused for another allocation. // // 3. Hang on a subsequent stop the world waiting for the P to stop, if the M // object is valid, but the M is exited, because startTheWorldWithSema didn't // actually wake anything to run the P. The P is _Pidle, but not in the pidle // list, thus startTheWorldWithSema will wake for it to actively stop. // // For this to go wrong, an exited M must fail to clear mp.self and must leave // the M on the sched.midle list. // // Similar to TraceSTW. func mexitSTW() { // Ensure we have multiple Ps, but not too many, as we want the // runnable goroutines likely to run on Ps with oldm set. runtime.GOMAXPROCS(4) // Background busy work so there is always something runnable. for i := range 2 { go traceSTWTarget(i) } // Wait for children to start running. ping.Store(1) for pong[0].Load() != 1 {} for pong[1].Load() != 1 {} for range 100 { // Exit a thread. The last P to run this will have it in oldm. go func() { runtime.LockOSThread() }() // STW var ms runtime.MemStats runtime.ReadMemStats(&ms) } stop.Store(true) println("OK") }