@@ -269,8 +269,8 @@ func withCancel(parent Context) *cancelCtx {
269
269
if parent == nil {
270
270
panic ("cannot create context from nil parent" )
271
271
}
272
- c := & cancelCtx {Context : parent }
273
- propagateCancel (parent , c )
272
+ c := & cancelCtx {}
273
+ c . propagateCancel (parent , c )
274
274
return c
275
275
}
276
276
@@ -289,48 +289,72 @@ func Cause(c Context) error {
289
289
return nil
290
290
}
291
291
292
- // goroutines counts the number of goroutines ever created; for testing.
293
- var goroutines atomic.Int32
294
-
295
- // propagateCancel arranges for child to be canceled when parent is.
296
- func propagateCancel (parent Context , child canceler ) {
297
- done := parent .Done ()
298
- if done == nil {
299
- return // parent is never canceled
292
+ // AfterFunc arranges to call f in its own goroutine after ctx is done
293
+ // (cancelled or timed out).
294
+ // If ctx is already done, AfterFunc calls f immediately in its own goroutine.
295
+ //
296
+ // Multiple calls to AfterFunc on a context operate independently;
297
+ // one does not replace another.
298
+ //
299
+ // Calling the returned stop function stops the association of ctx with f.
300
+ // It returns true if the call stopped f from being run.
301
+ // If stop returns false,
302
+ // either the context is done and f has been started in its own goroutine;
303
+ // or f was already stopped.
304
+ // The stop function does not wait for f to complete before returning.
305
+ // If the caller needs to know whether f is completed,
306
+ // it must coordinate with f explicitly.
307
+ //
308
+ // If ctx has a "AfterFunc(func()) func() bool" method,
309
+ // AfterFunc will use it to schedule the call.
310
+ func AfterFunc (ctx Context , f func ()) (stop func () bool ) {
311
+ a := & afterFuncCtx {
312
+ f : f ,
313
+ }
314
+ a .cancelCtx .propagateCancel (ctx , a )
315
+ return func () bool {
316
+ stopped := false
317
+ a .once .Do (func () {
318
+ stopped = true
319
+ })
320
+ if stopped {
321
+ a .cancel (true , Canceled , nil )
322
+ }
323
+ return stopped
300
324
}
325
+ }
301
326
302
- select {
303
- case <- done :
304
- // parent is already canceled
305
- child .cancel (false , parent .Err (), Cause (parent ))
306
- return
307
- default :
308
- }
327
+ type afterFuncer interface {
328
+ AfterFunc (func ()) func () bool
329
+ }
309
330
310
- if p , ok := parentCancelCtx (parent ); ok {
311
- p .mu .Lock ()
312
- if p .err != nil {
313
- // parent has already been canceled
314
- child .cancel (false , p .err , p .cause )
315
- } else {
316
- if p .children == nil {
317
- p .children = make (map [canceler ]struct {})
318
- }
319
- p .children [child ] = struct {}{}
320
- }
321
- p .mu .Unlock ()
322
- } else {
323
- goroutines .Add (1 )
324
- go func () {
325
- select {
326
- case <- parent .Done ():
327
- child .cancel (false , parent .Err (), Cause (parent ))
328
- case <- child .Done ():
329
- }
330
- }()
331
+ type afterFuncCtx struct {
332
+ cancelCtx
333
+ once sync.Once // either starts running f or stops f from running
334
+ f func ()
335
+ }
336
+
337
+ func (a * afterFuncCtx ) cancel (removeFromParent bool , err , cause error ) {
338
+ a .cancelCtx .cancel (false , err , cause )
339
+ if removeFromParent {
340
+ removeChild (a .Context , a )
331
341
}
342
+ a .once .Do (func () {
343
+ go a .f ()
344
+ })
332
345
}
333
346
347
+ // A stopCtx is used as the parent context of a cancelCtx when
348
+ // an AfterFunc has been registered with the parent.
349
+ // It holds the stop function used to unregister the AfterFunc.
350
+ type stopCtx struct {
351
+ Context
352
+ stop func () bool
353
+ }
354
+
355
+ // goroutines counts the number of goroutines ever created; for testing.
356
+ var goroutines atomic.Int32
357
+
334
358
// &cancelCtxKey is the key that a cancelCtx returns itself for.
335
359
var cancelCtxKey int
336
360
@@ -358,6 +382,10 @@ func parentCancelCtx(parent Context) (*cancelCtx, bool) {
358
382
359
383
// removeChild removes a context from its parent.
360
384
func removeChild (parent Context , child canceler ) {
385
+ if s , ok := parent .(stopCtx ); ok {
386
+ s .stop ()
387
+ return
388
+ }
361
389
p , ok := parentCancelCtx (parent )
362
390
if ! ok {
363
391
return
@@ -424,6 +452,64 @@ func (c *cancelCtx) Err() error {
424
452
return err
425
453
}
426
454
455
+ // propagateCancel arranges for child to be canceled when parent is.
456
+ // It sets the parent context of cancelCtx.
457
+ func (c * cancelCtx ) propagateCancel (parent Context , child canceler ) {
458
+ c .Context = parent
459
+
460
+ done := parent .Done ()
461
+ if done == nil {
462
+ return // parent is never canceled
463
+ }
464
+
465
+ select {
466
+ case <- done :
467
+ // parent is already canceled
468
+ child .cancel (false , parent .Err (), Cause (parent ))
469
+ return
470
+ default :
471
+ }
472
+
473
+ if p , ok := parentCancelCtx (parent ); ok {
474
+ // parent is a *cancelCtx, or derives from one.
475
+ p .mu .Lock ()
476
+ if p .err != nil {
477
+ // parent has already been canceled
478
+ child .cancel (false , p .err , p .cause )
479
+ } else {
480
+ if p .children == nil {
481
+ p .children = make (map [canceler ]struct {})
482
+ }
483
+ p .children [child ] = struct {}{}
484
+ }
485
+ p .mu .Unlock ()
486
+ return
487
+ }
488
+
489
+ if a , ok := parent .(afterFuncer ); ok {
490
+ // parent implements an AfterFunc method.
491
+ c .mu .Lock ()
492
+ stop := a .AfterFunc (func () {
493
+ child .cancel (false , parent .Err (), Cause (parent ))
494
+ })
495
+ c .Context = stopCtx {
496
+ Context : parent ,
497
+ stop : stop ,
498
+ }
499
+ c .mu .Unlock ()
500
+ return
501
+ }
502
+
503
+ goroutines .Add (1 )
504
+ go func () {
505
+ select {
506
+ case <- parent .Done ():
507
+ child .cancel (false , parent .Err (), Cause (parent ))
508
+ case <- child .Done ():
509
+ }
510
+ }()
511
+ }
512
+
427
513
type stringer interface {
428
514
String () string
429
515
}
@@ -533,10 +619,9 @@ func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, Cance
533
619
return WithCancel (parent )
534
620
}
535
621
c := & timerCtx {
536
- cancelCtx : cancelCtx {Context : parent },
537
- deadline : d ,
622
+ deadline : d ,
538
623
}
539
- propagateCancel (parent , c )
624
+ c . cancelCtx . propagateCancel (parent , c )
540
625
dur := time .Until (d )
541
626
if dur <= 0 {
542
627
c .cancel (true , DeadlineExceeded , cause ) // deadline has already passed
0 commit comments