From d90ce588eac7b9105c0ca556a7c6e975fd5c1eca Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Tue, 11 Jun 2024 11:02:18 -0700 Subject: internal/synctest: new package for testing concurrent code Add an internal (for now) implementation of testing/synctest. The synctest.Run function executes a tree of goroutines in an isolated environment using a fake clock. The synctest.Wait function allows a test to wait for all other goroutines within the test to reach a blocking point. For #67434 For #69687 Change-Id: Icb39e54c54cece96517e58ef9cfb18bf68506cfc Reviewed-on: https://go-review.googlesource.com/c/go/+/591997 Reviewed-by: Michael Pratt LUCI-TryBot-Result: Go LUCI --- src/runtime/testdata/testsynctest/main.go | 67 +++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/runtime/testdata/testsynctest/main.go (limited to 'src/runtime/testdata') diff --git a/src/runtime/testdata/testsynctest/main.go b/src/runtime/testdata/testsynctest/main.go new file mode 100644 index 0000000000..d2cbc99258 --- /dev/null +++ b/src/runtime/testdata/testsynctest/main.go @@ -0,0 +1,67 @@ +// 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" +) + +// 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() +} + +func main() { + synctest.Run(func() { + // Start the finalizer goroutine. + p := new(int) + runtime.SetFinalizer(p, func(*int) {}) + + startingCycles := numGCCycles() + ch1 := make(chan *int) + ch2 := make(chan *int) + defer close(ch1) + go func() { + for i := range ch1 { + v := *i + 1 + ch2 <- &v + } + }() + for { + // Make a lot of short-lived allocations to get the GC working. + for i := 0; i < 1000; i++ { + v := new(int) + *v = i + // Set finalizers on these values, just for added stress. + runtime.SetFinalizer(v, func(*int) {}) + 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 { + break + } + } + }) + println("success") +} -- cgit v1.3-5-g9baa