From a8564bd412d4495a6048f981d30d4d7abb1e45a7 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Thu, 14 Aug 2025 10:27:54 -0700 Subject: runtime: make all synctest bubble violations fatal panics Unblocking a bubbled goroutine from outside the bubble is an error and panics. Currently, some of those panics are regular panics and some are fatal. We use fatal panics in cases where its difficult to panic without leaving something in an inconsistent state. Change the regular panics (channel and timer operations) to be fatal. This makes our behavior more consistent: All bubble violations are always fatal. More importantly, it avoids introducing new, recoverable panics. A motivating example for this change is the context package, which performs channel operations with a mutex held in the expectation that those operations can never panic. These operations can now panic as a result of a bubble violation, potentially leaving a context.Context in an inconsistent state. Fixes #74837 Change-Id: Ie6efd916b7f505c0f13dde42de1572992401f15c Reviewed-on: https://go-review.googlesource.com/c/go/+/696195 Auto-Submit: Damien Neil LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt --- src/internal/synctest/synctest_test.go | 83 ++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 40 deletions(-) (limited to 'src/internal') diff --git a/src/internal/synctest/synctest_test.go b/src/internal/synctest/synctest_test.go index 6cebf86c31..307eee62e2 100644 --- a/src/internal/synctest/synctest_test.go +++ b/src/internal/synctest/synctest_test.go @@ -383,57 +383,59 @@ func TestChannelMovedOutOfBubble(t *testing.T) { for _, test := range []struct { desc string f func(chan struct{}) - wantPanic string + wantFatal string }{{ desc: "receive", f: func(ch chan struct{}) { <-ch }, - wantPanic: "receive on synctest channel from outside bubble", + wantFatal: "receive on synctest channel from outside bubble", }, { desc: "send", f: func(ch chan struct{}) { ch <- struct{}{} }, - wantPanic: "send on synctest channel from outside bubble", + wantFatal: "send on synctest channel from outside bubble", }, { desc: "close", f: func(ch chan struct{}) { close(ch) }, - wantPanic: "close of synctest channel from outside bubble", + wantFatal: "close of synctest channel from outside bubble", }} { t.Run(test.desc, func(t *testing.T) { // Bubbled channel accessed from outside any bubble. t.Run("outside_bubble", func(t *testing.T) { - donec := make(chan struct{}) - ch := make(chan chan struct{}) - go func() { - defer close(donec) - defer wantPanic(t, test.wantPanic) - test.f(<-ch) - }() - synctest.Run(func() { - ch <- make(chan struct{}) + wantFatal(t, test.wantFatal, func() { + donec := make(chan struct{}) + ch := make(chan chan struct{}) + go func() { + defer close(donec) + test.f(<-ch) + }() + synctest.Run(func() { + ch <- make(chan struct{}) + }) + <-donec }) - <-donec }) // Bubbled channel accessed from a different bubble. t.Run("different_bubble", func(t *testing.T) { - donec := make(chan struct{}) - ch := make(chan chan struct{}) - go func() { - defer close(donec) - c := <-ch + wantFatal(t, test.wantFatal, func() { + donec := make(chan struct{}) + ch := make(chan chan struct{}) + go func() { + defer close(donec) + c := <-ch + synctest.Run(func() { + test.f(c) + }) + }() synctest.Run(func() { - defer wantPanic(t, test.wantPanic) - test.f(c) + ch <- make(chan struct{}) }) - }() - synctest.Run(func() { - ch <- make(chan struct{}) + <-donec }) - <-donec }) }) } @@ -443,39 +445,40 @@ func TestTimerFromInsideBubble(t *testing.T) { for _, test := range []struct { desc string f func(tm *time.Timer) - wantPanic string + wantFatal string }{{ desc: "read channel", f: func(tm *time.Timer) { <-tm.C }, - wantPanic: "receive on synctest channel from outside bubble", + wantFatal: "receive on synctest channel from outside bubble", }, { desc: "Reset", f: func(tm *time.Timer) { tm.Reset(1 * time.Second) }, - wantPanic: "reset of synctest timer from outside bubble", + wantFatal: "reset of synctest timer from outside bubble", }, { desc: "Stop", f: func(tm *time.Timer) { tm.Stop() }, - wantPanic: "stop of synctest timer from outside bubble", + wantFatal: "stop of synctest timer from outside bubble", }} { t.Run(test.desc, func(t *testing.T) { - donec := make(chan struct{}) - ch := make(chan *time.Timer) - go func() { - defer close(donec) - defer wantPanic(t, test.wantPanic) - test.f(<-ch) - }() - synctest.Run(func() { - tm := time.NewTimer(1 * time.Second) - ch <- tm + wantFatal(t, test.wantFatal, func() { + donec := make(chan struct{}) + ch := make(chan *time.Timer) + go func() { + defer close(donec) + test.f(<-ch) + }() + synctest.Run(func() { + tm := time.NewTimer(1 * time.Second) + ch <- tm + }) + <-donec }) - <-donec }) } } -- cgit v1.3-5-g9baa