Skip to content

Commit ec4a9fb

Browse files
neildgopherbot
authored andcommitted
context: don't return a nil Cause for a canceled custom context
Avoid a case where Cause(ctx) could return nil for a canceled context, when ctx is a custom context implementation and descends from a cancellable-but-not-canceled first-party Context. Fixes #73258 Change-Id: Idbd81ccddea82ecabece4373d718baae6ca4b58e Reviewed-on: https://go-review.googlesource.com/c/go/+/663936 Reviewed-by: Alan Donovan <[email protected]> Auto-Submit: Damien Neil <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 3acd440 commit ec4a9fb

File tree

2 files changed

+98
-4
lines changed

2 files changed

+98
-4
lines changed

src/context/context.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,17 @@ func withCancel(parent Context) *cancelCtx {
288288
func Cause(c Context) error {
289289
if cc, ok := c.Value(&cancelCtxKey).(*cancelCtx); ok {
290290
cc.mu.Lock()
291-
defer cc.mu.Unlock()
292-
return cc.cause
291+
cause := cc.cause
292+
cc.mu.Unlock()
293+
if cause != nil {
294+
return cause
295+
}
296+
// Either this context is not canceled,
297+
// or it is canceled and the cancellation happened in a
298+
// custom context implementation rather than a *cancelCtx.
293299
}
294-
// There is no cancelCtxKey value, so we know that c is
295-
// not a descendant of some Context created by WithCancelCause.
300+
// There is no cancelCtxKey value with a cause, so we know that c is
301+
// not a descendant of some canceled Context created by WithCancelCause.
296302
// Therefore, there is no specific cause to return.
297303
// If this is not one of the standard Context types,
298304
// it might still have an error even though it won't have a cause.

src/context/x_test.go

+88
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,45 @@ func TestCause(t *testing.T) {
798798
err: nil,
799799
cause: nil,
800800
},
801+
{
802+
name: "parent of custom context not canceled",
803+
ctx: func() Context {
804+
ctx, _ := WithCancelCause(Background())
805+
ctx, cancel2 := newCustomContext(ctx)
806+
cancel2()
807+
return ctx
808+
},
809+
err: Canceled,
810+
cause: Canceled,
811+
},
812+
{
813+
name: "parent of custom context is canceled before",
814+
ctx: func() Context {
815+
ctx, cancel1 := WithCancelCause(Background())
816+
ctx, cancel2 := newCustomContext(ctx)
817+
cancel1(parentCause)
818+
cancel2()
819+
return ctx
820+
},
821+
err: Canceled,
822+
cause: parentCause,
823+
},
824+
{
825+
name: "parent of custom context is canceled after",
826+
ctx: func() Context {
827+
ctx, cancel1 := WithCancelCause(Background())
828+
ctx, cancel2 := newCustomContext(ctx)
829+
cancel2()
830+
cancel1(parentCause)
831+
return ctx
832+
},
833+
err: Canceled,
834+
// This isn't really right: the child context was canceled before
835+
// the parent context, and shouldn't inherit the parent's cause.
836+
// However, since the child is a custom context, Cause has no way
837+
// to tell which was canceled first and returns the parent's cause.
838+
cause: parentCause,
839+
},
801840
} {
802841
test := test
803842
t.Run(test.name, func(t *testing.T) {
@@ -1089,3 +1128,52 @@ func TestAfterFuncCalledAsynchronously(t *testing.T) {
10891128
t.Fatalf("AfterFunc not called after context is canceled")
10901129
}
10911130
}
1131+
1132+
// customContext is a custom Context implementation.
1133+
type customContext struct {
1134+
parent Context
1135+
1136+
doneOnce sync.Once
1137+
donec chan struct{}
1138+
err error
1139+
}
1140+
1141+
func newCustomContext(parent Context) (Context, CancelFunc) {
1142+
c := &customContext{
1143+
parent: parent,
1144+
donec: make(chan struct{}),
1145+
}
1146+
AfterFunc(parent, func() {
1147+
c.doneOnce.Do(func() {
1148+
c.err = parent.Err()
1149+
close(c.donec)
1150+
})
1151+
})
1152+
return c, func() {
1153+
c.doneOnce.Do(func() {
1154+
c.err = Canceled
1155+
close(c.donec)
1156+
})
1157+
}
1158+
}
1159+
1160+
func (c *customContext) Deadline() (time.Time, bool) {
1161+
return c.parent.Deadline()
1162+
}
1163+
1164+
func (c *customContext) Done() <-chan struct{} {
1165+
return c.donec
1166+
}
1167+
1168+
func (c *customContext) Err() error {
1169+
select {
1170+
case <-c.donec:
1171+
return c.err
1172+
default:
1173+
return nil
1174+
}
1175+
}
1176+
1177+
func (c *customContext) Value(key any) any {
1178+
return c.parent.Value(key)
1179+
}

0 commit comments

Comments
 (0)