blob: b022ef477756d1e6fbbcf99f9faba9864edd1710 [file] [log] [blame] [edit]
// 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")
}