| // Copyright 2024 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package main | |
| import ( | |
| "internal/synctest" | |
| "runtime" | |
| "runtime/metrics" | |
| "sync/atomic" | |
| "unsafe" | |
| ) | |
| // This program ensures system goroutines (GC workers, finalizer goroutine) | |
| // started from within a synctest bubble do not participate in that bubble. | |
| // | |
| // To ensure none of these goroutines start before synctest.Run, | |
| // it must have no dependencies on packages which may start system goroutines. | |
| // This includes the os package, which creates finalizers at init time. | |
| func numGCCycles() uint64 { | |
| samples := []metrics.Sample{{Name: "/gc/cycles/total:gc-cycles"}} | |
| metrics.Read(samples) | |
| if samples[0].Value.Kind() == metrics.KindBad { | |
| panic("metric not supported") | |
| } | |
| return samples[0].Value.Uint64() | |
| } | |
| type T struct { | |
| v int | |
| p unsafe.Pointer | |
| } | |
| func main() { | |
| // Channels created by a finalizer and cleanup func registered within the bubble. | |
| var ( | |
| finalizerCh atomic.Pointer[chan struct{}] | |
| cleanupCh atomic.Pointer[chan struct{}] | |
| ) | |
| synctest.Run(func() { | |
| // Start the finalizer and cleanup goroutines. | |
| { | |
| p := new(T) | |
| runtime.SetFinalizer(p, func(*T) { | |
| ch := make(chan struct{}) | |
| finalizerCh.Store(&ch) | |
| }) | |
| runtime.AddCleanup(p, func(struct{}) { | |
| ch := make(chan struct{}) | |
| cleanupCh.Store(&ch) | |
| }, struct{}{}) | |
| } | |
| startingCycles := numGCCycles() | |
| ch1 := make(chan *T) | |
| ch2 := make(chan *T) | |
| defer close(ch1) | |
| go func() { | |
| for range ch1 { | |
| ch2 <- &T{} | |
| } | |
| }() | |
| const iterations = 1000 | |
| for range iterations { | |
| // Make a lot of short-lived allocations to get the GC working. | |
| for range 1000 { | |
| v := new(T) | |
| // Set finalizers on these values, just for added stress. | |
| runtime.SetFinalizer(v, func(*T) {}) | |
| ch1 <- v | |
| <-ch2 | |
| } | |
| // If we've improperly put a GC goroutine into the synctest group, | |
| // this Wait is going to hang. | |
| synctest.Wait() | |
| // End the test after a couple of GC cycles have passed. | |
| if numGCCycles()-startingCycles > 1 && finalizerCh.Load() != nil && cleanupCh.Load() != nil { | |
| return | |
| } | |
| } | |
| println("finalizers/cleanups failed to run after", iterations, "cycles") | |
| }) | |
| // Close the channels created by the finalizer and cleanup func. | |
| // If the funcs improperly ran inside the bubble, these channels are bubbled | |
| // and trying to close them will panic. | |
| close(*finalizerCh.Load()) | |
| close(*cleanupCh.Load()) | |
| println("success") | |
| } | |