// 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.rangefunc package race_test import ( "runtime" "sync/atomic" "testing" ) type Seq2[T1, T2 any] func(yield func(T1, T2) bool) // ofSliceIndex returns a Seq over the elements of s. It is equivalent // to range s, except that it splits s into two halves and iterates // in two separate goroutines. This is racy if yield is racy, and yield // will be racy if it contains an early exit. func ofSliceIndex[T any, S ~[]T](s S) Seq2[int, T] { return func(yield func(int, T) bool) { c := make(chan bool, 2) var done atomic.Bool go func() { for i := 0; i < len(s)/2; i++ { if !done.Load() && !yield(i, s[i]) { done.Store(true) c <- false } } c <- true }() go func() { for i := len(s) / 2; i < len(s); i++ { if !done.Load() && !yield(i, s[i]) { done.Store(true) c <- false } } c <- true return }() if !<-c { return } <-c } } // foo is racy, or not, depending on the value of v // (0-4 == racy, otherwise, not racy). func foo(v int) int64 { var asum atomic.Int64 for i, x := range ofSliceIndex([]int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { if i%5 == v { break } asum.Add(x) // don't race on asum runtime.Gosched() } return 100 + asum.Load() } // TestRaceRangeFuncIterator races because x%5 can be equal to 4, // therefore foo can early exit. func TestRaceRangeFuncIterator(t *testing.T) { x := foo(4) t.Logf("foo(4)=%d", x) } // TestNoRaceRangeFuncIterator does not race because x%5 is never 5, // therefore foo's loop will not exit early, and this it will not race. func TestNoRaceRangeFuncIterator(t *testing.T) { x := foo(5) t.Logf("foo(5)=%d", x) }