Skip to content

Commit 8bec956

Browse files
committed
context: add APIs for setting a cancelation cause when deadline or timer expires
Fixes #56661 Change-Id: I1c23ebc52e6b7ae6ee956614e1a0a45d6ecbd5b4 Reviewed-on: https://go-review.googlesource.com/c/go/+/449318 Run-TryBot: Sameer Ajmani <[email protected]> Reviewed-by: Damien Neil <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 5996466 commit 8bec956

File tree

3 files changed

+75
-4
lines changed

3 files changed

+75
-4
lines changed

api/next/56661.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pkg context, func WithDeadlineCause(Context, time.Time, error) (Context, CancelFunc) #56661
2+
pkg context, func WithTimeoutCause(Context, time.Duration, error) (Context, CancelFunc) #56661

src/context/context.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,13 @@ func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
492492
// Canceling this context releases resources associated with it, so code should
493493
// call cancel as soon as the operations running in this Context complete.
494494
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
495+
return WithDeadlineCause(parent, d, nil)
496+
}
497+
498+
// WithDeadlineCause behaves like WithDeadline but also sets the cause of the
499+
// returned Context when the deadline is exceeded. The returned CancelFunc does
500+
// not set the cause.
501+
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc) {
495502
if parent == nil {
496503
panic("cannot create context from nil parent")
497504
}
@@ -506,14 +513,14 @@ func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
506513
propagateCancel(parent, c)
507514
dur := time.Until(d)
508515
if dur <= 0 {
509-
c.cancel(true, DeadlineExceeded, nil) // deadline has already passed
516+
c.cancel(true, DeadlineExceeded, cause) // deadline has already passed
510517
return c, func() { c.cancel(false, Canceled, nil) }
511518
}
512519
c.mu.Lock()
513520
defer c.mu.Unlock()
514521
if c.err == nil {
515522
c.timer = time.AfterFunc(dur, func() {
516-
c.cancel(true, DeadlineExceeded, nil)
523+
c.cancel(true, DeadlineExceeded, cause)
517524
})
518525
}
519526
return c, func() { c.cancel(true, Canceled, nil) }
@@ -567,6 +574,13 @@ func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
567574
return WithDeadline(parent, time.Now().Add(timeout))
568575
}
569576

577+
// WithTimeoutCause behaves like WithTimeout but also sets the cause of the
578+
// returned Context when the timout expires. The returned CancelFunc does
579+
// not set the cause.
580+
func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc) {
581+
return WithDeadlineCause(parent, time.Now().Add(timeout), cause)
582+
}
583+
570584
// WithValue returns a copy of parent in which the value associated with key is
571585
// val.
572586
//

src/context/context_test.go

+57-2
Original file line numberDiff line numberDiff line change
@@ -793,8 +793,11 @@ func XTestCustomContextGoroutines(t testingT) {
793793

794794
func XTestCause(t testingT) {
795795
var (
796-
parentCause = fmt.Errorf("parentCause")
797-
childCause = fmt.Errorf("childCause")
796+
forever = 1e6 * time.Second
797+
parentCause = fmt.Errorf("parentCause")
798+
childCause = fmt.Errorf("childCause")
799+
tooSlow = fmt.Errorf("tooSlow")
800+
finishedEarly = fmt.Errorf("finishedEarly")
798801
)
799802
for _, test := range []struct {
800803
name string
@@ -926,6 +929,58 @@ func XTestCause(t testingT) {
926929
err: DeadlineExceeded,
927930
cause: DeadlineExceeded,
928931
},
932+
{
933+
name: "WithTimeout canceled",
934+
ctx: func() Context {
935+
ctx, cancel := WithTimeout(Background(), forever)
936+
cancel()
937+
return ctx
938+
}(),
939+
err: Canceled,
940+
cause: Canceled,
941+
},
942+
{
943+
name: "WithTimeoutCause",
944+
ctx: func() Context {
945+
ctx, cancel := WithTimeoutCause(Background(), 0, tooSlow)
946+
cancel()
947+
return ctx
948+
}(),
949+
err: DeadlineExceeded,
950+
cause: tooSlow,
951+
},
952+
{
953+
name: "WithTimeoutCause canceled",
954+
ctx: func() Context {
955+
ctx, cancel := WithTimeoutCause(Background(), forever, tooSlow)
956+
cancel()
957+
return ctx
958+
}(),
959+
err: Canceled,
960+
cause: Canceled,
961+
},
962+
{
963+
name: "WithTimeoutCause stacked",
964+
ctx: func() Context {
965+
ctx, cancel := WithCancelCause(Background())
966+
ctx, _ = WithTimeoutCause(ctx, 0, tooSlow)
967+
cancel(finishedEarly)
968+
return ctx
969+
}(),
970+
err: DeadlineExceeded,
971+
cause: tooSlow,
972+
},
973+
{
974+
name: "WithTimeoutCause stacked canceled",
975+
ctx: func() Context {
976+
ctx, cancel := WithCancelCause(Background())
977+
ctx, _ = WithTimeoutCause(ctx, forever, tooSlow)
978+
cancel(finishedEarly)
979+
return ctx
980+
}(),
981+
err: Canceled,
982+
cause: finishedEarly,
983+
},
929984
} {
930985
if got, want := test.ctx.Err(), test.err; want != got {
931986
t.Errorf("%s: ctx.Err() = %v want %v", test.name, got, want)

0 commit comments

Comments
 (0)