runtime: use small struct TestSynctest to ensure cleanups run

Finalizers and cleanup funcs weren't running on the windows-arm64
builder. Put finalizers/cleanups on a small struct containing a pointer
rather than an *int, which fixes the problem.

Also uncomment a synctest.Wait that was accidentally commented out.

Fixes #73977

Change-Id: Ia6f18d74d6fccf2c5a9222317977c7458d67f158
Reviewed-on: https://go-review.googlesource.com/c/go/+/679696
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
Damien Neil 2025-06-06 12:59:04 -07:00
parent 848a768ba7
commit 985d600f3a

View File

@ -9,6 +9,7 @@ import (
"runtime" "runtime"
"runtime/metrics" "runtime/metrics"
"sync/atomic" "sync/atomic"
"unsafe"
) )
// This program ensures system goroutines (GC workers, finalizer goroutine) // This program ensures system goroutines (GC workers, finalizer goroutine)
@ -27,6 +28,11 @@ func numGCCycles() uint64 {
return samples[0].Value.Uint64() return samples[0].Value.Uint64()
} }
type T struct {
v int
p unsafe.Pointer
}
func main() { func main() {
// Channels created by a finalizer and cleanup func registered within the bubble. // Channels created by a finalizer and cleanup func registered within the bubble.
var ( var (
@ -36,8 +42,8 @@ func main() {
synctest.Run(func() { synctest.Run(func() {
// Start the finalizer and cleanup goroutines. // Start the finalizer and cleanup goroutines.
{ {
p := new(int) p := new(T)
runtime.SetFinalizer(p, func(*int) { runtime.SetFinalizer(p, func(*T) {
ch := make(chan struct{}) ch := make(chan struct{})
finalizerCh.Store(&ch) finalizerCh.Store(&ch)
}) })
@ -47,35 +53,35 @@ func main() {
}, struct{}{}) }, struct{}{})
} }
startingCycles := numGCCycles() startingCycles := numGCCycles()
ch1 := make(chan *int) ch1 := make(chan *T)
ch2 := make(chan *int) ch2 := make(chan *T)
defer close(ch1) defer close(ch1)
go func() { go func() {
for i := range ch1 { for range ch1 {
v := *i + 1 ch2 <- &T{}
ch2 <- &v
} }
}() }()
for { const iterations = 1000
for range iterations {
// Make a lot of short-lived allocations to get the GC working. // Make a lot of short-lived allocations to get the GC working.
for i := 0; i < 1000; i++ { for range 1000 {
v := new(int) v := new(T)
*v = i
// Set finalizers on these values, just for added stress. // Set finalizers on these values, just for added stress.
runtime.SetFinalizer(v, func(*int) {}) runtime.SetFinalizer(v, func(*T) {})
ch1 <- v ch1 <- v
<-ch2 <-ch2
} }
// If we've improperly put a GC goroutine into the synctest group, // If we've improperly put a GC goroutine into the synctest group,
// this Wait is going to hang. // this Wait is going to hang.
//synctest.Wait() synctest.Wait()
// End the test after a couple of GC cycles have passed. // End the test after a couple of GC cycles have passed.
if numGCCycles()-startingCycles > 1 && finalizerCh.Load() != nil && cleanupCh.Load() != nil { if numGCCycles()-startingCycles > 1 && finalizerCh.Load() != nil && cleanupCh.Load() != nil {
break return
} }
} }
println("finalizers/cleanups failed to run after", iterations, "cycles")
}) })
// Close the channels created by the finalizer and cleanup func. // Close the channels created by the finalizer and cleanup func.
// If the funcs improperly ran inside the bubble, these channels are bubbled // If the funcs improperly ran inside the bubble, these channels are bubbled