Skip to content

Commit 8ca209e

Browse files
neildgopherbot
authored andcommitted
context: don't return a non-nil from Err before Done is closed
The Context.Err documentation states that it returns nil if the context's done channel is not closed. Fix a race condition introduced by CL 653795 where Err could return a non-nil error slightly before the Done channel is closed. No impact on Err performance when returning nil. Slows down Err when returning non-nil by about 3x, but that's still almost 2x faster than before CL 653795 and the performance of this path is less important. (A tight loop checking Err for doneness will be terminated by the first Err call to return a non-nil result.) goos: darwin goarch: arm64 pkg: context cpu: Apple M4 Pro │ /tmp/bench.0 │ /tmp/bench.1 │ │ sec/op │ sec/op vs base │ ErrOK-14 1.806n ± 1% 1.774n ± 0% -1.77% (p=0.000 n=8) ErrCanceled-14 1.821n ± 1% 7.525n ± 3% +313.23% (p=0.000 n=8) geomean 1.813n 3.654n +101.47% Fixes golang#75533 Change-Id: Iea22781a199ace7e7f70cf65168c36e090cd2e2a Reviewed-on: https://go-review.googlesource.com/c/go/+/705235 TryBot-Bypass: Damien Neil <[email protected]> Reviewed-by: Nicholas Husin <[email protected]> Reviewed-by: Nicholas Husin <[email protected]> Auto-Submit: Damien Neil <[email protected]>
1 parent 3032894 commit 8ca209e

File tree

2 files changed

+22
-0
lines changed

2 files changed

+22
-0
lines changed

src/context/context.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,8 @@ func (c *cancelCtx) Done() <-chan struct{} {
463463
func (c *cancelCtx) Err() error {
464464
// An atomic load is ~5x faster than a mutex, which can matter in tight loops.
465465
if err := c.err.Load(); err != nil {
466+
// Ensure the done channel has been closed before returning a non-nil error.
467+
<-c.Done()
466468
return err.(error)
467469
}
468470
return nil

src/context/x_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,3 +1177,23 @@ func (c *customContext) Err() error {
11771177
func (c *customContext) Value(key any) any {
11781178
return c.parent.Value(key)
11791179
}
1180+
1181+
// Issue #75533.
1182+
func TestContextErrDoneRace(t *testing.T) {
1183+
// 4 iterations reliably reproduced #75533.
1184+
for range 10 {
1185+
ctx, cancel := WithCancel(Background())
1186+
donec := ctx.Done()
1187+
go cancel()
1188+
for ctx.Err() == nil {
1189+
if runtime.GOARCH == "wasm" {
1190+
runtime.Gosched() // need to explicitly yield
1191+
}
1192+
}
1193+
select {
1194+
case <-donec:
1195+
default:
1196+
t.Fatalf("ctx.Err is non-nil, but ctx.Done is not closed")
1197+
}
1198+
}
1199+
}

0 commit comments

Comments
 (0)