Skip to content

Commit

Permalink
testing/synctest: add experimental synctest package
Browse files Browse the repository at this point in the history
The testing/synctest package is experimental,
subject to change or removal,
and only present when GOEXPERIMENT=synctest.

Fixes #69687

Change-Id: I6c79e7d1479a54bebcd2de754854c87bfd0a5fa1
Reviewed-on: https://go-review.googlesource.com/c/go/+/629735
Reviewed-by: Michael Pratt <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
  • Loading branch information
neild committed Nov 20, 2024
1 parent a7043ec commit 76e4efd
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 47 deletions.
3 changes: 2 additions & 1 deletion src/go/build/deps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,8 @@ var depsRules = `
< testing;
RUNTIME
< internal/synctest;
< internal/synctest
< testing/synctest;
log/slog, testing
< testing/slogtest;
Expand Down
3 changes: 3 additions & 0 deletions src/internal/goexperiment/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,7 @@ type Flags struct {

// SyncHashTrieMap enables the HashTrieMap sync.Map implementation.
SyncHashTrieMap bool

// Synctest enables the testing/synctest package.
Synctest bool
}
48 changes: 2 additions & 46 deletions src/internal/synctest/synctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,16 @@
// license that can be found in the LICENSE file.

// Package synctest provides support for testing concurrent code.
//
// See the testing/synctest package for function documentation.
package synctest

import (
_ "unsafe" // for go:linkname
)

// Run executes f in a new goroutine.
//
// The new goroutine and any goroutines transitively started by it form
// an isolated "bubble".
// Run waits for all goroutines in the bubble to exit before returning.
//
// Goroutines in the bubble use a synthetic time implementation.
// The initial time is midnight UTC 2000-01-01.
//
// Time advances when every goroutine in the bubble is blocked.
// For example, a call to time.Sleep will block until all other
// goroutines are blocked and return after the bubble's clock has
// advanced. See [Wait] for the specific definition of blocked.
//
// If every goroutine is blocked and there are no timers scheduled,
// Run panics.
//
// Channels, time.Timers, and time.Tickers created within the bubble
// are associated with it. Operating on a bubbled channel, timer, or ticker
// from outside the bubble panics.
//
//go:linkname Run
func Run(f func())

// Wait blocks until every goroutine within the current bubble,
// other than the current goroutine, is durably blocked.
// It panics if called from a non-bubbled goroutine,
// or if two goroutines in the same bubble call Wait at the same time.
//
// A goroutine is durably blocked if can only be unblocked by another
// goroutine in its bubble. The following operations durably block
// a goroutine:
// - a send or receive on a channel from within the bubble
// - a select statement where every case is a channel within the bubble
// - sync.Cond.Wait
// - time.Sleep
//
// A goroutine executing a system call or waiting for an external event
// such as a network operation is not durably blocked.
// For example, a goroutine blocked reading from an network connection
// is not durably blocked even if no data is currently available on the
// connection, because it may be unblocked by data written from outside
// the bubble or may be in the process of receiving data from a kernel
// network buffer.
//
// A goroutine is not durably blocked when blocked on a send or receive
// on a channel that was not created within its bubble, because it may
// be unblocked by a channel receive or send from outside its bubble.
//
//go:linkname Wait
func Wait()
67 changes: 67 additions & 0 deletions src/testing/synctest/synctest.go
Original file line number Diff line number Diff line change
@@ -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.

//go:build goexperiment.synctest

// Package synctest provides support for testing concurrent code.
//
// This package only exists when using Go compiled with GOEXPERIMENT=synctest.
// It is experimental, and not subject to the Go 1 compatibility promise.
package synctest

import (
"internal/synctest"
)

// Run executes f in a new goroutine.
//
// The new goroutine and any goroutines transitively started by it form
// an isolated "bubble".
// Run waits for all goroutines in the bubble to exit before returning.
//
// Goroutines in the bubble use a synthetic time implementation.
// The initial time is midnight UTC 2000-01-01.
//
// Time advances when every goroutine in the bubble is blocked.
// For example, a call to time.Sleep will block until all other
// goroutines are blocked and return after the bubble's clock has
// advanced. See [Wait] for the specific definition of blocked.
//
// If every goroutine is blocked and there are no timers scheduled,
// Run panics.
//
// Channels, time.Timers, and time.Tickers created within the bubble
// are associated with it. Operating on a bubbled channel, timer, or ticker
// from outside the bubble panics.
func Run(f func()) {
synctest.Run(f)
}

// Wait blocks until every goroutine within the current bubble,
// other than the current goroutine, is durably blocked.
// It panics if called from a non-bubbled goroutine,
// or if two goroutines in the same bubble call Wait at the same time.
//
// A goroutine is durably blocked if can only be unblocked by another
// goroutine in its bubble. The following operations durably block
// a goroutine:
// - a send or receive on a channel from within the bubble
// - a select statement where every case is a channel within the bubble
// - sync.Cond.Wait
// - time.Sleep
//
// A goroutine executing a system call or waiting for an external event
// such as a network operation is not durably blocked.
// For example, a goroutine blocked reading from an network connection
// is not durably blocked even if no data is currently available on the
// connection, because it may be unblocked by data written from outside
// the bubble or may be in the process of receiving data from a kernel
// network buffer.
//
// A goroutine is not durably blocked when blocked on a send or receive
// on a channel that was not created within its bubble, because it may
// be unblocked by a channel receive or send from outside its bubble.
func Wait() {
synctest.Wait()
}

0 comments on commit 76e4efd

Please sign in to comment.