1// Copyright 2024 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5//go:build goexperiment.rangefunc 6 7package race_test 8 9import ( 10 "runtime" 11 "sync/atomic" 12 "testing" 13) 14 15type Seq2[T1, T2 any] func(yield func(T1, T2) bool) 16 17// ofSliceIndex returns a Seq over the elements of s. It is equivalent 18// to range s, except that it splits s into two halves and iterates 19// in two separate goroutines. This is racy if yield is racy, and yield 20// will be racy if it contains an early exit. 21func ofSliceIndex[T any, S ~[]T](s S) Seq2[int, T] { 22 return func(yield func(int, T) bool) { 23 c := make(chan bool, 2) 24 var done atomic.Bool 25 go func() { 26 for i := 0; i < len(s)/2; i++ { 27 if !done.Load() && !yield(i, s[i]) { 28 done.Store(true) 29 c <- false 30 } 31 } 32 c <- true 33 }() 34 go func() { 35 for i := len(s) / 2; i < len(s); i++ { 36 if !done.Load() && !yield(i, s[i]) { 37 done.Store(true) 38 c <- false 39 } 40 } 41 c <- true 42 return 43 }() 44 if !<-c { 45 return 46 } 47 <-c 48 } 49} 50 51// foo is racy, or not, depending on the value of v 52// (0-4 == racy, otherwise, not racy). 53func foo(v int) int64 { 54 var asum atomic.Int64 55 for i, x := range ofSliceIndex([]int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) { 56 if i%5 == v { 57 break 58 } 59 asum.Add(x) // don't race on asum 60 runtime.Gosched() 61 } 62 return 100 + asum.Load() 63} 64 65// TestRaceRangeFuncIterator races because x%5 can be equal to 4, 66// therefore foo can early exit. 67func TestRaceRangeFuncIterator(t *testing.T) { 68 x := foo(4) 69 t.Logf("foo(4)=%d", x) 70} 71 72// TestNoRaceRangeFuncIterator does not race because x%5 is never 5, 73// therefore foo's loop will not exit early, and this it will not race. 74func TestNoRaceRangeFuncIterator(t *testing.T) { 75 x := foo(5) 76 t.Logf("foo(5)=%d", x) 77} 78