"internal/coverage"
"io"
"reflect"
+ "sync/atomic"
"unsafe"
)
// inconsistency when reading the counter array from the thread
// running ClearCoverageCounters.
- var sd []uint32
+ var sd []atomic.Uint32
bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&sd))
for _, c := range cl {
bufHdr.Cap = int(c.Len)
for i := 0; i < len(sd); i++ {
// Skip ahead until the next non-zero value.
- if sd[i] == 0 {
+ sdi := sd[i].Load()
+ if sdi == 0 {
continue
}
// We found a function that was executed; clear its counters.
- nCtrs := sd[i]
+ nCtrs := sdi
for j := 0; j < int(nCtrs); j++ {
- sd[i+coverage.FirstCtrOffset+j] = 0
+ sd[i+coverage.FirstCtrOffset+j].Store(0)
}
// Move to next function.
i += coverage.FirstCtrOffset + int(nCtrs) - 1
"path/filepath"
"reflect"
"runtime"
+ "sync/atomic"
"time"
"unsafe"
)
}
func (s *emitState) NumFuncs() (int, error) {
- var sd []uint32
+ var sd []atomic.Uint32
bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&sd))
totalFuncs := 0
bufHdr.Cap = int(c.Len)
for i := 0; i < len(sd); i++ {
// Skip ahead until the next non-zero value.
- if sd[i] == 0 {
+ sdi := sd[i].Load()
+ if sdi == 0 {
continue
}
// We found a function that was executed.
- nCtrs := sd[i]
+ nCtrs := sdi
// Check to make sure that we have at least one live
// counter. See the implementation note in ClearCoverageCounters
st := i + coverage.FirstCtrOffset
counters := sd[st : st+int(nCtrs)]
for i := 0; i < len(counters); i++ {
- if counters[i] != 0 {
+ if counters[i].Load() != 0 {
isLive = true
break
}
}
func (s *emitState) VisitFuncs(f encodecounter.CounterVisitorFn) error {
- var sd []uint32
+ var sd []atomic.Uint32
+ var tcounters []uint32
bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&sd))
+ rdCounters := func(actrs []atomic.Uint32, ctrs []uint32) []uint32 {
+ ctrs = ctrs[:0]
+ for i := range actrs {
+ ctrs = append(ctrs, actrs[i].Load())
+ }
+ return ctrs
+ }
+
dpkg := uint32(0)
for _, c := range s.counterlist {
bufHdr.Data = uintptr(unsafe.Pointer(c.Counters))
bufHdr.Cap = int(c.Len)
for i := 0; i < len(sd); i++ {
// Skip ahead until the next non-zero value.
- if sd[i] == 0 {
+ sdi := sd[i].Load()
+ if sdi == 0 {
continue
}
// We found a function that was executed.
- nCtrs := sd[i+coverage.NumCtrsOffset]
- pkgId := sd[i+coverage.PkgIdOffset]
- funcId := sd[i+coverage.FuncIdOffset]
+ nCtrs := sd[i+coverage.NumCtrsOffset].Load()
+ pkgId := sd[i+coverage.PkgIdOffset].Load()
+ funcId := sd[i+coverage.FuncIdOffset].Load()
cst := i + coverage.FirstCtrOffset
counters := sd[cst : cst+int(nCtrs)]
// for a description of why this is needed.
isLive := false
for i := 0; i < len(counters); i++ {
- if counters[i] != 0 {
+ if counters[i].Load() != 0 {
isLive = true
break
}
pkgId--
}
- if err := f(pkgId, funcId, counters); err != nil {
+ tcounters = rdCounters(counters, tcounters)
+ if err := f(pkgId, funcId, tcounters); err != nil {
return err
}
"fmt"
"internal/coverage"
"internal/goexperiment"
+ "internal/platform"
"internal/testenv"
"os"
"os/exec"
"path/filepath"
+ "runtime"
"strings"
"testing"
)
t.Errorf("error output does not contain %q: %s", want, output)
}
}
+
+func TestIssue56006EmitDataRaceCoverRunningGoroutine(t *testing.T) {
+ // This test requires "go test -race -cover", meaning that we need
+ // go build, go run, and "-race" support.
+ testenv.MustHaveGoRun(t)
+ if !platform.RaceDetectorSupported(runtime.GOOS, runtime.GOARCH) ||
+ !testenv.HasCGO() {
+ t.Skip("skipped due to lack of race detector support / CGO")
+ }
+
+ // This will run a program with -cover and -race where we have a
+ // goroutine still running (and updating counters) at the point where
+ // the test runtime is trying to write out counter data.
+ cmd := exec.Command(testenv.GoToolPath(t), "test", "-cover", "-race")
+ cmd.Dir = filepath.Join("testdata", "issue56006")
+ b, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("go test -cover -race failed: %v", err)
+ }
+
+ // Don't want to see any data races in output.
+ avoid := []string{"DATA RACE"}
+ for _, no := range avoid {
+ if strings.Contains(string(b), no) {
+ t.Logf("%s\n", string(b))
+ t.Fatalf("found %s in test output, not permitted", no)
+ }
+ }
+}
--- /dev/null
+package main
+
+//go:noinline
+func blah(x int) int {
+ if x != 0 {
+ return x + 42
+ }
+ return x - 42
+}
+
+func main() {
+ go infloop()
+ println(blah(1) + blah(0))
+}
+
+var G int
+
+func infloop() {
+ for {
+ G += blah(1)
+ G += blah(0)
+ if G > 10000 {
+ G = 0
+ }
+ }
+}
--- /dev/null
+package main
+
+import "testing"
+
+func TestSomething(t *testing.T) {
+ go infloop()
+ println(blah(1) + blah(0))
+}