runtime: clarify stack traces for bubbled goroutines

Use the synctest bubble ID to identify bubbles in traces,
rather than the goroutine ID of the bubble's root goroutine.

Some waitReasons include a "(synctest)" suffix to distinguish
a durably blocking state from a non-durable one. For example,
"chan send" vs. "chan send (synctest)". Change this suffix
to "(durable)".

Always print a "(durable)" sufix for the state of durably
blocked bubbled goroutines. For example, print "sleep (durable)".

Drop the "[not] durably blocked" text from goroutine states,
since this is now entirely redundant with the waitReason.

Old:
  goroutine 8 [chan receive (synctest), synctest bubble 7, durably blocked]:
  goroutine 9 [select (no cases), synctest bubble 7, durably blocked]:

New:
  goroutine 8 [chan receive (durable), synctest bubble 1]:
  goroutine 9 [select (no cases) (durable), synctest bubble 1]:

Change-Id: I89112efb25150a98a2954f54d1910ccec52a5824
Reviewed-on: https://go-review.googlesource.com/c/go/+/679376
Auto-Submit: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
Damien Neil 2025-06-05 14:21:47 -07:00 committed by Gopher Robot
parent 049a5e6036
commit 848a768ba7
2 changed files with 22 additions and 19 deletions

View File

@ -1093,10 +1093,10 @@ const (
waitReasonGCWeakToStrongWait // "GC weak to strong wait" waitReasonGCWeakToStrongWait // "GC weak to strong wait"
waitReasonSynctestRun // "synctest.Run" waitReasonSynctestRun // "synctest.Run"
waitReasonSynctestWait // "synctest.Wait" waitReasonSynctestWait // "synctest.Wait"
waitReasonSynctestChanReceive // "chan receive (synctest)" waitReasonSynctestChanReceive // "chan receive (durable)"
waitReasonSynctestChanSend // "chan send (synctest)" waitReasonSynctestChanSend // "chan send (durable)"
waitReasonSynctestSelect // "select (synctest)" waitReasonSynctestSelect // "select (durable)"
waitReasonSynctestWaitGroupWait // "sync.WaitGroup.Wait (synctest)" waitReasonSynctestWaitGroupWait // "sync.WaitGroup.Wait (durable)"
waitReasonCleanupWait // "cleanup wait" waitReasonCleanupWait // "cleanup wait"
) )
@ -1143,10 +1143,10 @@ var waitReasonStrings = [...]string{
waitReasonGCWeakToStrongWait: "GC weak to strong wait", waitReasonGCWeakToStrongWait: "GC weak to strong wait",
waitReasonSynctestRun: "synctest.Run", waitReasonSynctestRun: "synctest.Run",
waitReasonSynctestWait: "synctest.Wait", waitReasonSynctestWait: "synctest.Wait",
waitReasonSynctestChanReceive: "chan receive (synctest)", waitReasonSynctestChanReceive: "chan receive (durable)",
waitReasonSynctestChanSend: "chan send (synctest)", waitReasonSynctestChanSend: "chan send (durable)",
waitReasonSynctestSelect: "select (synctest)", waitReasonSynctestSelect: "select (durable)",
waitReasonSynctestWaitGroupWait: "sync.WaitGroup.Wait (synctest)", waitReasonSynctestWaitGroupWait: "sync.WaitGroup.Wait (durable)",
waitReasonCleanupWait: "cleanup wait", waitReasonCleanupWait: "cleanup wait",
} }

View File

@ -1248,6 +1248,13 @@ func goroutineheader(gp *g) {
if isScan { if isScan {
print(" (scan)") print(" (scan)")
} }
if bubble := gp.bubble; bubble != nil &&
gp.waitreason.isIdleInSynctest() &&
!stringslite.HasSuffix(status, "(durable)") {
// If this isn't a status where the name includes a (durable)
// suffix to distinguish it from the non-durable form, add it here.
print(" (durable)")
}
if waitfor >= 1 { if waitfor >= 1 {
print(", ", waitfor, " minutes") print(", ", waitfor, " minutes")
} }
@ -1255,11 +1262,7 @@ func goroutineheader(gp *g) {
print(", locked to thread") print(", locked to thread")
} }
if bubble := gp.bubble; bubble != nil { if bubble := gp.bubble; bubble != nil {
print(", synctest bubble ", bubble.root.goid, ", ") print(", synctest bubble ", bubble.id)
if !gp.waitreason.isIdleInSynctest() {
print("not ")
}
print("durably blocked")
} }
print("]:\n") print("]:\n")
} }