github.com/haraldrudell/parl@v0.4.176/cancel-context.go (about) 1 /* 2 © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package parl 7 8 import ( 9 "context" 10 "errors" 11 12 "github.com/haraldrudell/parl/perrors" 13 ) 14 15 // ErrNotCancelContext indicates that InvokeCancel was provided a context 16 // not a return value from NewCancelContext or NewCancelContextFunc. 17 // 18 // Test for ErrNotCancelContext: 19 // 20 // if errors.Is(err, parl.ErrNotCancelContext) … 21 var ErrNotCancelContext = errors.New("context chain does not have CancelContext") 22 23 // cancelContextKey is a unique named type for storing and retrieving cancelFunc 24 // - used with [context.WithValue] 25 type cancelContextKey string 26 27 // cancelKey is a unique value for storing and retrieving cancelFunc 28 // - used with [context.WithValue] 29 var cancelKey cancelContextKey = "parl.NewCancelContext" 30 31 // NewCancelContext creates a cancelable context without managing a CancelFunction value 32 // - NewCancelContext is like [context.WithCancel] but has the CancelFunc embedded. 33 // - after use, [InvokeCancel] must be invoked with cancelCtx as argument to 34 // release resources 35 // - — 36 // - for unexported code in context package to work, a separate type cannot be used 37 // 38 // Usage: 39 // 40 // ctx := NewCancelContext(context.Background()) 41 // … 42 // InvokeCancel(ctx) 43 func NewCancelContext(ctx context.Context) (cancelCtx context.Context) { 44 return NewCancelContextFunc(context.WithCancel(ctx)) 45 } 46 47 // NewCancelContextFunc stores the cancel function cancel in the context ctx. 48 // the returned context can be provided to InvokeCancel to cancel the context. 49 func NewCancelContextFunc(ctx context.Context, cancel context.CancelFunc) (cancelCtx context.Context) { 50 return context.WithValue(ctx, cancelKey, cancel) 51 } 52 53 // HasCancel return if ctx can be used with [parl.InvokeCancel] 54 // - such contexts are returned by [parl.NewCancelContext] 55 func HasCancel(ctx context.Context) (hasCancel bool) { 56 if ctx == nil { 57 return 58 } 59 cancel, _ := ctx.Value(cancelKey).(context.CancelFunc) 60 return cancel != nil 61 } 62 63 // InvokeCancel cancels the last CancelContext in ctx’ chain of contexts 64 // - ctx must have been returned by either NewCancelContext or NewCancelContextFunc 65 // - ctx nil is panic 66 // - ctx not from NewCancelContext or NewCancelContextFunc is panic 67 // - thread-safe, idempotent, deferrable 68 func InvokeCancel(ctx context.Context) { 69 invokeCancel(ctx) 70 } 71 72 // CancelOnError invokes InvokeCancel if errp has an error. 73 // - CancelOnError is deferrable and thread-safe. 74 // - ctx must have been returned by either NewCancelContext or NewCancelContextFunc. 75 // - errp == nil or *errp == nil means no error 76 // - ctx nil is panic 77 // - ctx not from NewCancelContext or NewCancelContextFunc is panic 78 // - thread-safe, idempotent 79 func CancelOnError(errp *error, ctx context.Context) { 80 if errp == nil || *errp == nil { 81 return // there was no error 82 } 83 invokeCancel(ctx) 84 } 85 86 // invokeCancel is one extra stack frame from invoker 87 // - invoked via: 88 // - — [parl.InvokeCancel] 89 // - — [parl.CancelOnError] 90 // - — [parl.OnceWaiter.Cancel] 91 func invokeCancel(ctx context.Context) { 92 if ctx == nil { 93 panic(perrors.NewPF("ctx cannot be nil")) 94 } 95 96 // retrieve cancel function from the nearest context.valueCtx 97 var cancel context.CancelFunc 98 var ok bool 99 if cancel, ok = ctx.Value(cancelKey).(context.CancelFunc); !ok { 100 panic(perrors.ErrorfPF("%w", ErrNotCancelContext)) 101 } 102 103 // invoke the function canceling the context.valueCtx parent that is context.cancelCtx 104 // - and all its child contexts 105 cancel() 106 107 handleContextNotify(ctx) 108 }