1
0
mirror of https://github.com/golang/go synced 2024-11-05 15:16:11 -07:00

context: support non-standard Context in Cause

If Cause is called on a non-standard Context, call ctx.Err.

Fixes #62582

Change-Id: Iac4ed93203eb5529f8839eb479b6ee2ee5ff6cbc
Reviewed-on: https://go-review.googlesource.com/c/go/+/527277
Reviewed-by: Bryan Mills <bcmills@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
This commit is contained in:
Ian Lance Taylor 2023-09-11 16:43:20 -07:00 committed by Gopher Robot
parent 399b2a4b1b
commit fccd0b9b70
2 changed files with 126 additions and 1 deletions

View File

@ -286,7 +286,12 @@ func Cause(c Context) error {
defer cc.mu.Unlock()
return cc.cause
}
return nil
// There is no cancelCtxKey value, so we know that c is
// not a descendant of some 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()
}
// AfterFunc arranges to call f in its own goroutine after ctx is done

View File

@ -867,6 +867,126 @@ func TestCustomContextPropagation(t *testing.T) {
}
}
// customCauseContext is a custom Context used to test context.Cause.
type customCauseContext struct {
mu sync.Mutex
done chan struct{}
err error
cancelChild CancelFunc
}
func (ccc *customCauseContext) Deadline() (deadline time.Time, ok bool) {
return
}
func (ccc *customCauseContext) Done() <-chan struct{} {
ccc.mu.Lock()
defer ccc.mu.Unlock()
return ccc.done
}
func (ccc *customCauseContext) Err() error {
ccc.mu.Lock()
defer ccc.mu.Unlock()
return ccc.err
}
func (ccc *customCauseContext) Value(key any) any {
return nil
}
func (ccc *customCauseContext) cancel() {
ccc.mu.Lock()
ccc.err = Canceled
close(ccc.done)
cancelChild := ccc.cancelChild
ccc.mu.Unlock()
if cancelChild != nil {
cancelChild()
}
}
func (ccc *customCauseContext) setCancelChild(cancelChild CancelFunc) {
ccc.cancelChild = cancelChild
}
func TestCustomContextCause(t *testing.T) {
// Test if we cancel a custom context, Err and Cause return Canceled.
ccc := &customCauseContext{
done: make(chan struct{}),
}
ccc.cancel()
if got := ccc.Err(); got != Canceled {
t.Errorf("ccc.Err() = %v, want %v", got, Canceled)
}
if got := Cause(ccc); got != Canceled {
t.Errorf("Cause(ccc) = %v, want %v", got, Canceled)
}
// Test that if we pass a custom context to WithCancelCause,
// and then cancel that child context with a cause,
// that the cause of the child canceled context is correct
// but that the parent custom context is not canceled.
ccc = &customCauseContext{
done: make(chan struct{}),
}
ctx, causeFunc := WithCancelCause(ccc)
cause := errors.New("TestCustomContextCause")
causeFunc(cause)
if got := ctx.Err(); got != Canceled {
t.Errorf("after CancelCauseFunc ctx.Err() = %v, want %v", got, Canceled)
}
if got := Cause(ctx); got != cause {
t.Errorf("after CancelCauseFunc Cause(ctx) = %v, want %v", got, cause)
}
if got := ccc.Err(); got != nil {
t.Errorf("after CancelCauseFunc ccc.Err() = %v, want %v", got, nil)
}
if got := Cause(ccc); got != nil {
t.Errorf("after CancelCauseFunc Cause(ccc) = %v, want %v", got, nil)
}
// Test that if we now cancel the parent custom context,
// the cause of the child canceled context is still correct,
// and the parent custom context is canceled without a cause.
ccc.cancel()
if got := ctx.Err(); got != Canceled {
t.Errorf("after CancelCauseFunc ctx.Err() = %v, want %v", got, Canceled)
}
if got := Cause(ctx); got != cause {
t.Errorf("after CancelCauseFunc Cause(ctx) = %v, want %v", got, cause)
}
if got := ccc.Err(); got != Canceled {
t.Errorf("after CancelCauseFunc ccc.Err() = %v, want %v", got, Canceled)
}
if got := Cause(ccc); got != Canceled {
t.Errorf("after CancelCauseFunc Cause(ccc) = %v, want %v", got, Canceled)
}
// Test that if we associate a custom context with a child,
// then canceling the custom context cancels the child.
ccc = &customCauseContext{
done: make(chan struct{}),
}
ctx, cancelFunc := WithCancel(ccc)
ccc.setCancelChild(cancelFunc)
ccc.cancel()
if got := ctx.Err(); got != Canceled {
t.Errorf("after CancelCauseFunc ctx.Err() = %v, want %v", got, Canceled)
}
if got := Cause(ctx); got != Canceled {
t.Errorf("after CancelCauseFunc Cause(ctx) = %v, want %v", got, Canceled)
}
if got := ccc.Err(); got != Canceled {
t.Errorf("after CancelCauseFunc ccc.Err() = %v, want %v", got, Canceled)
}
if got := Cause(ccc); got != Canceled {
t.Errorf("after CancelCauseFunc Cause(ccc) = %v, want %v", got, Canceled)
}
}
func TestAfterFuncCalledAfterCancel(t *testing.T) {
ctx, cancel := WithCancel(Background())
donec := make(chan struct{})