context: don't return the wrong error when Cause races cancellation

Check to see if a context is canceled at all
before checking for the cancellaion cause.
If we can't find a cause, use the original error.

Avoids a data race where we look for a cause,
find none (because the context is not canceled),
the context is canceled,
and we then return ctx.Err() (even though there is now a cause).

Fixes #73390

Change-Id: I97f44aef25c6b02871d987970abfb4c215c5c80e
Reviewed-on: https://go-review.googlesource.com/c/go/+/679835
Reviewed-by: Sean Liao <sean@liao.dev>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
Damien Neil 2025-06-06 15:38:28 -07:00
parent c6f882f6c5
commit 1a53ce9734

View file

@ -286,6 +286,10 @@ func withCancel(parent Context) *cancelCtx {
// Otherwise Cause(c) returns the same value as c.Err().
// Cause returns nil if c has not been canceled yet.
func Cause(c Context) error {
err := c.Err()
if err == nil {
return nil
}
if cc, ok := c.Value(&cancelCtxKey).(*cancelCtx); ok {
cc.mu.Lock()
cause := cc.cause
@ -293,16 +297,12 @@ func Cause(c Context) error {
if cause != nil {
return cause
}
// Either this context is not canceled,
// or it is canceled and the cancellation happened in a
// custom context implementation rather than a *cancelCtx.
// The parent cancelCtx doesn't have a cause,
// so c must have been canceled in some custom context implementation.
}
// There is no cancelCtxKey value with a cause, so we know that c is
// not a descendant of some canceled Context created by WithCancelCause.
// Therefore, there is no specific cause to return.
// If this is not one of the standard Context types,
// it might still have an error even though it won't have a cause.
return c.Err()
// We don't have a cause to return from a parent cancelCtx,
// so return the context's error.
return err
}
// AfterFunc arranges to call f in its own goroutine after ctx is canceled.