// changegstatus is called when the non-lock status of a g changes.
// It is never called with a Gscanstatus.
func (sg *synctestGroup) changegstatus(gp *g, oldval, newval uint32) {
- lock(&sg.mu)
+ // Determine whether this change in status affects the idleness of the group.
+ // If this isn't a goroutine starting, stopping, durably blocking,
+ // or waking up after durably blocking, then return immediately without
+ // locking sg.mu.
+ //
+ // For example, stack growth (newstack) will changegstatus
+ // from _Grunning to _Gcopystack. This is uninteresting to synctest,
+ // but if stack growth occurs while sg.mu is held, we must not recursively lock.
+ totalDelta := 0
wasRunning := true
switch oldval {
case _Gdead:
wasRunning = false
- sg.total++
+ totalDelta++
case _Gwaiting:
if gp.waitreason.isIdleInSynctest() {
wasRunning = false
switch newval {
case _Gdead:
isRunning = false
- sg.total--
+ totalDelta--
case _Gwaiting:
if gp.waitreason.isIdleInSynctest() {
isRunning = false
}
}
+ // It's possible for wasRunning == isRunning while totalDelta != 0;
+ // for example, if a new goroutine is created in a non-running state.
+ if wasRunning == isRunning && totalDelta == 0 {
+ return
+ }
+
+ lock(&sg.mu)
+ sg.total += totalDelta
if wasRunning != isRunning {
if isRunning {
sg.running++