Skip to content

Commit fce9d45

Browse files
neildgopherbot
authored andcommitted
runtime, testing/synctest: verify cleanups/finalizers run outside bubbles
Cleanup functions and finalizers must not run in a synctest bubble. If they did, a function run by the GC at an unpredictable time could unblock a bubble that synctest believes is durably blocked. Add a test verifying that cleanups and finalizers are always run by non-bubbled goroutines. (This is already the case because we never add system goroutines to a bubble.) For #67434 Change-Id: I5a48db2b26f9712c3b0dc1f425d99814031a2fc1 Reviewed-on: https://go-review.googlesource.com/c/go/+/675257 Reviewed-by: Michael Pratt <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Damien Neil <[email protected]>
1 parent b78e380 commit fce9d45

File tree

2 files changed

+29
-6
lines changed

2 files changed

+29
-6
lines changed

src/runtime/testdata/testsynctest/main.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"internal/synctest"
99
"runtime"
1010
"runtime/metrics"
11+
"sync/atomic"
1112
)
1213

1314
// This program ensures system goroutines (GC workers, finalizer goroutine)
@@ -27,11 +28,24 @@ func numGCCycles() uint64 {
2728
}
2829

2930
func main() {
31+
// Channels created by a finalizer and cleanup func registered within the bubble.
32+
var (
33+
finalizerCh atomic.Pointer[chan struct{}]
34+
cleanupCh atomic.Pointer[chan struct{}]
35+
)
3036
synctest.Run(func() {
31-
// Start the finalizer goroutine.
32-
p := new(int)
33-
runtime.SetFinalizer(p, func(*int) {})
34-
37+
// Start the finalizer and cleanup goroutines.
38+
{
39+
p := new(int)
40+
runtime.SetFinalizer(p, func(*int) {
41+
ch := make(chan struct{})
42+
finalizerCh.Store(&ch)
43+
})
44+
runtime.AddCleanup(p, func(struct{}) {
45+
ch := make(chan struct{})
46+
cleanupCh.Store(&ch)
47+
}, struct{}{})
48+
}
3549
startingCycles := numGCCycles()
3650
ch1 := make(chan *int)
3751
ch2 := make(chan *int)
@@ -55,13 +69,18 @@ func main() {
5569

5670
// If we've improperly put a GC goroutine into the synctest group,
5771
// this Wait is going to hang.
58-
synctest.Wait()
72+
//synctest.Wait()
5973

6074
// End the test after a couple of GC cycles have passed.
61-
if numGCCycles()-startingCycles > 1 {
75+
if numGCCycles()-startingCycles > 1 && finalizerCh.Load() != nil && cleanupCh.Load() != nil {
6276
break
6377
}
6478
}
6579
})
80+
// Close the channels created by the finalizer and cleanup func.
81+
// If the funcs improperly ran inside the bubble, these channels are bubbled
82+
// and trying to close them will panic.
83+
close(*finalizerCh.Load())
84+
close(*cleanupCh.Load())
6685
println("success")
6786
}

src/testing/synctest/synctest.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@
8383
// is associated with it. Operating on a bubbled channel, timer, or
8484
// ticker from outside the bubble panics.
8585
//
86+
// Cleanup functions and finalizers registered with
87+
// [runtime.AddCleanup] and [runtime.SetFinalizer]
88+
// run outside of any bubble.
89+
//
8690
// # Example: Context.AfterFunc
8791
//
8892
// This example demonstrates testing the [context.AfterFunc] function.

0 commit comments

Comments
 (0)