@@ -162,24 +162,71 @@ type CancelFunc func()
162
162
// Canceling this context releases resources associated with it, so code should
163
163
// call cancel as soon as the operations running in this Context complete.
164
164
func WithCancel (parent Context ) (ctx Context , cancel CancelFunc ) {
165
+ c := withCancel (parent )
166
+ return c , func () { c .cancel (true , Canceled , nil ) }
167
+ }
168
+
169
+ // A CancelCauseFunc behaves like a [CancelFunc] but additionally sets the cancellation cause.
170
+ // This cause can be retrieved by calling [Cause] on the canceled Context or on
171
+ // any of its derived Contexts.
172
+ //
173
+ // If the context has already been canceled, CancelCauseFunc does not set the cause.
174
+ // For example, if childContext is derived from parentContext:
175
+ // - if parentContext is canceled with cause1 before childContext is canceled with cause2,
176
+ // then Cause(parentContext) == Cause(childContext) == cause1
177
+ // - if childContext is canceled with cause2 before parentContext is canceled with cause1,
178
+ // then Cause(parentContext) == cause1 and Cause(childContext) == cause2
179
+ type CancelCauseFunc func (cause error )
180
+
181
+ // WithCancelCause behaves like [WithCancel] but returns a [CancelCauseFunc] instead of a [CancelFunc].
182
+ // Calling cancel with a non-nil error (the "cause") records that error in ctx;
183
+ // it can then be retrieved using Cause(ctx).
184
+ // Calling cancel with nil sets the cause to Canceled.
185
+ //
186
+ // Example use:
187
+ //
188
+ // ctx, cancel := context.WithCancelCause(parent)
189
+ // cancel(myError)
190
+ // ctx.Err() // returns context.Canceled
191
+ // context.Cause(ctx) // returns myError
192
+ func WithCancelCause (parent Context ) (ctx Context , cancel CancelCauseFunc ) {
193
+ c := withCancel (parent )
194
+ return c , func (cause error ) { c .cancel (true , Canceled , cause ) }
195
+ }
196
+
197
+ func withCancel (parent Context ) * cancelCtx {
165
198
if parent == nil {
166
199
panic ("cannot create context from nil parent" )
167
200
}
168
- c := newCancelCtx (parent )
169
- propagateCancel (parent , & c )
170
- return & c , func () { c .cancel (true , Canceled ) }
201
+ c := newCancelCtx ()
202
+ c .propagateCancel (parent , c )
203
+ return c
204
+ }
205
+
206
+ // Cause returns a non-nil error explaining why c was canceled.
207
+ // The first cancellation of c or one of its parents sets the cause.
208
+ // If that cancellation happened via a call to CancelCauseFunc(err),
209
+ // then [Cause] returns err.
210
+ // Otherwise Cause(c) returns the same value as c.Err().
211
+ // Cause returns nil if c has not been canceled yet.
212
+ func Cause (c Context ) error {
213
+ if cc , ok := c .Value (& cancelCtxKey ).(* cancelCtx ); ok {
214
+ return cc .cause
215
+ }
216
+ return nil
171
217
}
172
218
173
219
// newCancelCtx returns an initialized cancelCtx.
174
- func newCancelCtx (parent Context ) cancelCtx {
175
- return cancelCtx {
176
- Context : parent ,
177
- done : NewChannel [struct {}](),
220
+ func newCancelCtx () * cancelCtx {
221
+ return & cancelCtx {
222
+ done : NewChannel [struct {}](),
178
223
}
179
224
}
180
225
181
226
// propagateCancel arranges for child to be canceled when parent is.
182
- func propagateCancel (parent Context , child canceler ) {
227
+ func (c * cancelCtx ) propagateCancel (parent Context , child canceler ) {
228
+ c .Context = parent
229
+
183
230
done := parent .Done ()
184
231
if done == nil {
185
232
return // parent is never canceled
@@ -189,7 +236,7 @@ func propagateCancel(parent Context, child canceler) {
189
236
parent ,
190
237
Receive (done , func (ctx Context , _ struct {}, _ bool ) {
191
238
// Parent is already canceled
192
- child .cancel (false , parent .Err ())
239
+ child .cancel (false , parent .Err (), Cause ( parent ) )
193
240
}),
194
241
Default (func (_ Context ) {
195
242
// Ignore
@@ -199,7 +246,7 @@ func propagateCancel(parent Context, child canceler) {
199
246
if p , ok := parentCancelCtx (parent ); ok {
200
247
if p .err != nil {
201
248
// parent has already been canceled
202
- child .cancel (false , p .err )
249
+ child .cancel (false , p .err , p . cause )
203
250
} else {
204
251
if p .children == nil {
205
252
p .children = make (map [canceler ]struct {})
@@ -257,7 +304,7 @@ func removeChild(parent Context, child canceler) {
257
304
// A canceler is a context type that can be canceled directly. The
258
305
// implementations are *cancelCtx and *timerCtx.
259
306
type canceler interface {
260
- cancel (removeFromParent bool , err error )
307
+ cancel (removeFromParent bool , err , cause error )
261
308
Done () Channel [struct {}]
262
309
}
263
310
@@ -276,6 +323,7 @@ type cancelCtx struct {
276
323
done Channel [struct {}]
277
324
children map [canceler ]struct {} // set to nil by the first cancel call
278
325
err error // set to non-nil by the first cancel call
326
+ cause error // set to non-nil by the first cancel call
279
327
}
280
328
281
329
func (c * cancelCtx ) Value (key interface {}) interface {} {
@@ -296,21 +344,25 @@ func (c *cancelCtx) Err() error {
296
344
297
345
// cancel closes c.done, cancels each of c's children, and, if
298
346
// removeFromParent is true, removes c from its parent's children.
299
- func (c * cancelCtx ) cancel (removeFromParent bool , err error ) {
347
+ func (c * cancelCtx ) cancel (removeFromParent bool , err , cause error ) {
300
348
if err == nil {
301
349
panic ("context: internal error: missing cancel error" )
302
350
}
351
+ if cause == nil {
352
+ cause = err
353
+ }
303
354
if c .err != nil {
304
355
return // already canceled
305
356
}
306
357
c .err = err
358
+ c .cause = cause
307
359
if c .done == nil {
308
360
c .done = closedchan
309
361
} else {
310
362
c .done .Close ()
311
363
}
312
364
for child := range c .children {
313
- child .cancel (false , err )
365
+ child .cancel (false , err , cause )
314
366
}
315
367
c .children = nil
316
368
0 commit comments