]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: return a different bubble deadlock error when main goroutine is done
authorDamien Neil <dneil@google.com>
Thu, 5 Jun 2025 20:55:35 +0000 (13:55 -0700)
committerGopher Robot <gobot@golang.org>
Mon, 9 Jun 2025 17:55:42 +0000 (10:55 -0700)
The synctest.Test function waits for all goroutines in a bubble to
exit before returning. If there is ever a point when all goroutines
in a bubble are durably blocked, it panics and reports a deadlock.

Panic with a different message depending on whether the bubble's
main goroutine has returned or not. The main goroutine returning
stops the bubble clock, so knowing whether it is running or not
is useful debugging information.

The new panic messages are:
deadlock: all goroutines in bubble are blocked
deadlock: main bubble goroutine has exited but blocked goroutines remain

Change-Id: I94a69e79121c272d9c86f412c1c9c7de57ef27ef
Reviewed-on: https://go-review.googlesource.com/c/go/+/679375
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/internal/synctest/synctest_test.go
src/runtime/synctest.go

index c2f84be736d5757d5afd34ace4c747823abdf764..fe6eb63702b5c4a04d59b4353d96b3943f7ebfc4 100644 (file)
@@ -488,7 +488,7 @@ func TestDeadlockRoot(t *testing.T) {
 }
 
 func TestDeadlockChild(t *testing.T) {
-       defer wantPanic(t, "deadlock: all goroutines in bubble are blocked")
+       defer wantPanic(t, "deadlock: main bubble goroutine has exited but blocked goroutines remain")
        synctest.Run(func() {
                go func() {
                        select {}
@@ -497,7 +497,7 @@ func TestDeadlockChild(t *testing.T) {
 }
 
 func TestDeadlockTicker(t *testing.T) {
-       defer wantPanic(t, "deadlock: all goroutines in bubble are blocked")
+       defer wantPanic(t, "deadlock: main bubble goroutine has exited but blocked goroutines remain")
        synctest.Run(func() {
                go func() {
                        for range time.Tick(1 * time.Second) {
index c837c792a53e8301ff02044085dd25555a9914f7..08a0e5d444d8291e9950201c28dd465286ecbdfd 100644 (file)
@@ -242,7 +242,13 @@ func synctestRun(f func()) {
                raceacquireg(gp, gp.bubble.raceaddr())
        }
        if total != 1 {
-               panic(synctestDeadlockError{bubble})
+               var reason string
+               if bubble.done {
+                       reason = "deadlock: main bubble goroutine has exited but blocked goroutines remain"
+               } else {
+                       reason = "deadlock: all goroutines in bubble are blocked"
+               }
+               panic(synctestDeadlockError{reason: reason, bubble: bubble})
        }
        if gp.timer != nil && gp.timer.isFake {
                // Verify that we haven't marked this goroutine's sleep timer as fake.
@@ -252,11 +258,12 @@ func synctestRun(f func()) {
 }
 
 type synctestDeadlockError struct {
+       reason string
        bubble *synctestBubble
 }
 
-func (synctestDeadlockError) Error() string {
-       return "deadlock: all goroutines in bubble are blocked"
+func (synctestDeadlockError) Error() string {
+       return e.reason
 }
 
 func synctestidle_c(gp *g, _ unsafe.Pointer) bool {