mirror of
https://github.com/golang/go
synced 2024-11-18 13:54:59 -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:
parent
399b2a4b1b
commit
fccd0b9b70
@ -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
|
||||
|
@ -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{})
|
||||
|
Loading…
Reference in New Issue
Block a user