Skip to content

Commit e6ce5de

Browse files
committed
testing: fix racy t.done usage by adding a separate race-canary field
1 parent eaa7d9f commit e6ce5de

File tree

2 files changed

+26
-16
lines changed

2 files changed

+26
-16
lines changed

src/testing/fuzz.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,7 @@ func fRunner(f *F, fn func(*F)) {
710710

711711
// Report after all subtests have finished.
712712
f.report()
713+
f.doneRaceCanary = true
713714
f.done = true
714715
f.setRan()
715716
}()

src/testing/testing.go

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -593,20 +593,21 @@ const maxStackLen = 50
593593
// common holds the elements common between T and B and
594594
// captures common methods such as Errorf.
595595
type common struct {
596-
mu sync.RWMutex // guards this group of fields
597-
output []byte // Output generated by test or benchmark.
598-
w io.Writer // For flushToParent.
599-
ran bool // Test or benchmark (or one of its subtests) was executed.
600-
failed bool // Test or benchmark has failed.
601-
skipped bool // Test or benchmark has been skipped.
602-
done bool // Test is finished and all subtests have completed.
603-
helperPCs map[uintptr]struct{} // functions to be skipped when writing file/line info
604-
helperNames map[string]struct{} // helperPCs converted to function names
605-
cleanups []func() // optional functions to be called at the end of the test
606-
cleanupName string // Name of the cleanup function.
607-
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
608-
finished bool // Test function has completed.
609-
inFuzzFn bool // Whether the fuzz target, if this is one, is running.
596+
mu sync.RWMutex // guards this group of fields
597+
output []byte // Output generated by test or benchmark.
598+
w io.Writer // For flushToParent.
599+
ran bool // Test or benchmark (or one of its subtests) was executed.
600+
failed bool // Test or benchmark has failed.
601+
skipped bool // Test or benchmark has been skipped.
602+
done bool // Test is finished and all subtests have completed.
603+
doneRaceCanary bool // Shadows all interactions with done except test-completion to trigger a race on misbehavior.
604+
helperPCs map[uintptr]struct{} // functions to be skipped when writing file/line info
605+
helperNames map[string]struct{} // helperPCs converted to function names
606+
cleanups []func() // optional functions to be called at the end of the test
607+
cleanupName string // Name of the cleanup function.
608+
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
609+
finished bool // Test function has completed.
610+
inFuzzFn bool // Whether the fuzz target, if this is one, is running.
610611

611612
chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
612613
bench bool // Whether the current test is a benchmark.
@@ -949,6 +950,7 @@ func (c *common) Fail() {
949950
c.mu.Lock()
950951
defer c.mu.Unlock()
951952
// c.done needs to be locked to synchronize checks to c.done in parent tests.
953+
_ = c.doneRaceCanary
952954
if c.done {
953955
panic("Fail in goroutine after " + c.name + " has completed")
954956
}
@@ -960,6 +962,7 @@ func (c *common) Failed() bool {
960962
c.mu.RLock()
961963
defer c.mu.RUnlock()
962964

965+
_ = c.doneRaceCanary
963966
if !c.done && int64(race.Errors()) > c.lastRaceErrors.Load() {
964967
c.mu.RUnlock()
965968
c.checkRaces()
@@ -1015,12 +1018,14 @@ func (c *common) log(s string) {
10151018
func (c *common) logDepth(s string, depth int) {
10161019
c.mu.Lock()
10171020
defer c.mu.Unlock()
1021+
_ = c.doneRaceCanary
10181022
if c.done {
10191023
// This test has already finished. Try and log this message
10201024
// with our parent. If we don't have a parent, panic.
10211025
for parent := c.parent; parent != nil; parent = parent.parent {
10221026
parent.mu.Lock()
10231027
defer parent.mu.Unlock()
1028+
_ = parent.doneRaceCanary
10241029
if !parent.done {
10251030
parent.output = append(parent.output, parent.decorate(s, depth+1)...)
10261031
return
@@ -1672,9 +1677,13 @@ func tRunner(t *T, fn func(t *T)) {
16721677
}
16731678
t.report() // Report after all subtests have finished.
16741679

1675-
// Do not lock t.done to allow race detector to detect race in case
1676-
// the user does not appropriately synchronize a goroutine.
1680+
// Do not lock around t.done's race canary to allow race detector to detect
1681+
// cases where the user does not appropriately synchronize a goroutine...
1682+
t.doneRaceCanary = true
1683+
t.mu.Lock()
1684+
// ...but do lock around t.done so after-done behavior is consistent.
16771685
t.done = true
1686+
t.mu.Unlock()
16781687
if t.parent != nil && !t.hasSub.Load() {
16791688
t.setRan()
16801689
}

0 commit comments

Comments
 (0)