github.com/haraldrudell/parl@v0.4.176/add-notifier.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 "sync/atomic" 11 ) 12 13 const ( 14 // counts [parl.handleContextNotify] and [parl.invokeCancel] 15 cancelNotifierFrames = 2 16 ) 17 18 // a NotifierFunc receives a stack trace of function causing cancel 19 // - typically stack trace begins with [parl.InvokeCancel] 20 type NotifierFunc func(slice Stack) 21 22 // notifier1Key is the context value key for child-context notifiers 23 var notifier1Key cancelContextKey = "notifyChild" 24 25 // notifierAllKey is the context value key for all-context notifiers 26 var notifierAllKey cancelContextKey = "notifyAll" 27 28 // threadSafeList is a thread-safe slice for all-cancel notifers 29 type threadSafeList struct { 30 // - atomic.Pointer.Load for thread-safe read 31 // - CompareAndSwap of cloned list for thread-safe write 32 notifiers atomic.Pointer[[]NotifierFunc] 33 } 34 35 // AddNotifier1 adds a function that is invoked when a child context is canceled 36 // - child contexts with their own AddNotifier1 are not detected 37 // - invocation is immediately after context cancel completes 38 // - implemented by inserting a value into the context chain 39 // - notifier receives a stack trace of the cancel invocation, 40 // typically beginning with [parl.InvokeCancel] 41 // - notifier should be thread-safe and not long running 42 // - typical usage is debug of unexpected context cancel 43 func AddNotifier1(ctx context.Context, notifier NotifierFunc) (ctx2 context.Context) { 44 if ctx == nil { 45 panic(NilError("ctx")) 46 } else if notifier == nil { 47 panic(NilError("notifier")) 48 } 49 return context.WithValue(ctx, notifier1Key, notifier) 50 } 51 52 // AddNotifier adds a function that is invoked when any context is canceled 53 // - AddNotifier is typically invoked on the root context 54 // - any InvokeCancel in the context tree below the top AddNotifier 55 // invocation causes notification 56 // - invocation is immediately after context cancel completes 57 // - implemented by inserting a thread-safe slice value into the context chain 58 // - notifier receives a stack trace of the cancel invocation, 59 // typically beginning with [parl.InvokeCancel] 60 // - notifier should be thread-safe and not long running 61 // - typical usage is debug of unexpected context cancel 62 func AddNotifier(ctx context.Context, notifier NotifierFunc) (ctx2 context.Context) { 63 if ctx == nil { 64 panic(NilError("ctx")) 65 } else if notifier == nil { 66 panic(NilError("notifier")) 67 } 68 69 // if this context-chain has static notifier, append to it 70 if list, ok := ctx.Value(notifierAllKey).(*threadSafeList); ok { // ok only if non-nil 71 for { 72 // currentSlicep is read-only to be thread-safe 73 var currentSlicep = list.notifiers.Load() 74 // clone and append 75 var newSlice = append(append([]NotifierFunc{}, *currentSlicep...), notifier) 76 if list.notifiers.CompareAndSwap(currentSlicep, &newSlice) { 77 ctx2 = ctx // appended: ctx does not change 78 return // append return 79 } 80 } 81 } 82 83 // create a new list 84 var newList threadSafeList 85 var newSlice = []NotifierFunc{notifier} 86 newList.notifiers.Store(&newSlice) 87 88 // insert list pointer into context chain 89 ctx2 = context.WithValue(ctx, notifierAllKey, &newList) 90 return // insert context value return, ctx2 new value 91 } 92 93 // handleContextNotify is invoked for all CancelContext cancel invocations 94 func handleContextNotify(ctx context.Context) { 95 // fetch the nearest notify1 function 96 // - notify1 are created by [parl.AddNotifier1] and are notified of 97 // cancellation of a child context 98 var notifier, _ = ctx.Value(notifier1Key).(NotifierFunc) 99 100 // fetch any notifyall list 101 var notifiers []NotifierFunc 102 if list, ok := ctx.Value(notifierAllKey).(*threadSafeList); ok { 103 notifiers = *list.notifiers.Load() 104 } 105 106 if notifier == nil && len(notifiers) == 0 { 107 return // no notifiers return 108 } 109 110 // stack trace for notifiers: expensive 111 var cl = newStack(cancelNotifierFrames) 112 113 // invoke all notifier functions 114 if notifier != nil { 115 notifier(cl) 116 } 117 for _, n := range notifiers { 118 n(cl) 119 } 120 }