github.com/haraldrudell/parl@v0.4.176/iters/base-iterator.go (about) 1 /* 2 © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package iters 7 8 import ( 9 "errors" 10 "sync" 11 "sync/atomic" 12 13 "github.com/haraldrudell/parl/internal/cyclebreaker" 14 "github.com/haraldrudell/parl/perrors" 15 ) 16 17 // BaseIterator implements: 18 // - [Iterator] methods [Iterator.Cond] [Iterator.Next] [Iterator.Cancel] 19 // - consumer must: 20 // - — implement [Iterator.Init] since it returns the enclosing type 21 // - — provide IteratorAction[T] 22 type BaseIterator[T any] struct { 23 iteratorAction IteratorAction[T] 24 25 // cancelState is updated by: 26 // - Cancel() and 27 // - enqueueForFn() inside i.publicsLock lock 28 // - is state because Cancel is not complete until iteratorAction has returned 29 cancelState atomic.Uint32 30 // publicsLock serializes invocations of i.Next and i.Cancel 31 publicsLock sync.Mutex 32 // err contains any errors returned by fn other than parl.ErrEndCallbacks 33 // - nil if fn is active 34 // - non-nil pointer to nil error if fn completed without error 35 // - — returned parl.ErrEndCallbacks 36 // - — returned from FunctionIteratorCancel -1 invocation without error 37 // - non-nil error if fn returned error 38 // - updated by enqueueForFn() inside i.publicsLock lock 39 err atomic.Pointer[error] 40 asyncCancel func() 41 } 42 43 // NewBaseIterator returns an implementation of Cond Next Cancel methods part of [iters.Iterator] 44 // - asyncCancel is used if function or converter iterators are blocking. 45 // asyncCancel indicates that a Cancel invocation has occurred 46 func NewBaseIterator[T any]( 47 iteratorAction IteratorAction[T], 48 asyncCancel ...func(), 49 ) (iterator *BaseIterator[T]) { 50 if iteratorAction == nil { 51 panic(cyclebreaker.NilError("iteratorAction")) 52 } 53 var ac func() 54 if len(asyncCancel) > 0 { 55 ac = asyncCancel[0] 56 } 57 return &BaseIterator[T]{ 58 iteratorAction: iteratorAction, 59 asyncCancel: ac, 60 } 61 } 62 63 // Cond implements the condition statement of a Go “for” clause 64 // - condition is true if iterationVariable was assigned a value and the iteration should continue 65 // - the iterationVariable is updated by being provided as a pointer. 66 // iterationVariable cannot be nil 67 // - errp is an optional error pointer receiving any errors during iterator execution 68 // 69 // Usage: 70 // 71 // for i, iterator := iters.NewSlicePointerIterator(someSlice).Init(); iterator.Cond(&i); { 72 // // i is pointer to slice element 73 func (i *BaseIterator[T]) Cond(iterationVariablep *T, errp ...*error) (condition bool) { 74 if iterationVariablep == nil { 75 cyclebreaker.NilError("iterationVariablep") 76 } 77 78 // outside lock check updating errp 79 if hasError, _ := i.getErr(replaceErrp, errp...); hasError { 80 return // iterator is canceled 81 } 82 83 // next value 84 var value T 85 if value, condition = i.Next(); condition { 86 *iterationVariablep = value 87 } else if len(errp) > 0 { 88 // collect any error for errp 89 i.getErr(replaceErrp, errp...) 90 } 91 92 return // condition and iterationVariablep updated, errp unchanged 93 } 94 95 // Next advances to next item and returns it 96 // - if hasValue true, value contains the next value 97 // - otherwise, no more items exist and value is the data type zero-value 98 func (i *BaseIterator[T]) Next() (value T, hasValue bool) { 99 100 // fast outside-lock value-check 101 if i.err.Load() != nil { 102 return // no more values: zero-value and hasValue false 103 } 104 i.publicsLock.Lock() 105 defer i.publicsLock.Unlock() 106 107 // inside-lock check 108 if i.err.Load() != nil { 109 return // no more values: zero-value and hasValue false 110 } 111 value, hasValue = i.doAction(doNext) 112 113 return // hasValue true, valid value return 114 } 115 116 // Cancel stops an iteration 117 // - after Cancel invocation, Cond, Next and Same indicate no value available 118 // - Cancel returns the first error that occurred during iteration, if any 119 // - an iterator implementation may require Cancel invocation 120 // to release resources 121 // - Cancel is deferrable 122 func (i *BaseIterator[T]) Cancel(errp ...*error) (err error) { 123 124 // fast outside-lock check 125 var hasErr bool 126 if hasErr, err = i.getErr(appendErrp, errp...); hasErr { 127 return // already canceled 128 } 129 // ensure cancel initiated prior to lock 130 if cancelStates(i.cancelState.Load()) == notCanceled { 131 i.cancelState.CompareAndSwap(uint32(notCanceled), uint32(cancelRequested)) 132 } 133 // asyncCancel 134 if ac := i.asyncCancel; ac != nil { 135 ac() 136 } 137 // the lock provides wait mechanic 138 i.publicsLock.Lock() 139 defer i.publicsLock.Unlock() 140 141 // inside lock check 142 if hasErr, err = i.getErr(appendErrp, errp...); hasErr { 143 return // already canceled 144 } 145 // send cancel to iteratorAction 146 i.doAction(doCancel) 147 _, err = i.getErr(appendErrp, errp...) 148 149 return 150 } 151 152 const ( 153 // [BaseIterator.doAction] invoked for next value 154 doNext anAction = false 155 // [BaseIterator.doAction] invoked for cancel 156 doCancel anAction = true 157 ) 158 159 // Action for [BaseIterator.enqueueForFn] 160 // - doNext doCancel 161 type anAction bool 162 163 // doAction invokes [BaseIterator.iteratorAction] 164 // - invoked while holding [BaseIterator.publicsLock] 165 // - [BaseIterator.err] should have been checked inside lock 166 // - on return: 167 // - — if hasValue is true, value is valid 168 // - — [BaseIterator.err] may be updated 169 // - — 170 // - recovers from [BaseIterator.iteratorAction] panic 171 func (i *BaseIterator[T]) doAction(action anAction) (value T, hasValue bool) { 172 173 // cancelState inside lock prior to invoking iteratorAction 174 var cancelState = cancelStates(i.cancelState.Load()) 175 if cancelState == cancelRequested { 176 action = doCancel // after Cancel invoked, the only action is cancel 177 } 178 defer cyclebreaker.Recover(func() cyclebreaker.DA { return cyclebreaker.A() }, nil, i.setErr) 179 180 v, err := i.iteratorAction(bool(action)) 181 182 // determine if value is valid 183 if hasValue = action == doNext && err == nil; hasValue { 184 value = v 185 } 186 187 // update state: cancelComplete endOfData errorReceived 188 var nextState cancelStates 189 if err != nil { 190 if !errors.Is(err, cyclebreaker.ErrEndCallbacks) { 191 // received an unknown error 192 nextState = errorReceived 193 } else { 194 // received end-of-data from iteratorAction 195 err = nil // ignore the error 196 if cancelState == notCanceled { 197 // spontaneous end of data 198 nextState = endOfData 199 } else { 200 // end-of-data return from cancel invocation 201 nextState = cancelComplete 202 } 203 } 204 } else if cancelState == cancelRequested { 205 // non-error return from cancel invocation 206 nextState = cancelComplete 207 } 208 if nextState != notCanceled { 209 i.setErr2(err, nextState) 210 } 211 212 return 213 } 214 215 const ( 216 // [BaseIterator.getErr] uses [perrors.AppendError] 217 appendErrp getErrAppend = false 218 // [BaseIterator.getErr] replaces *errp 219 replaceErrp getErrAppend = true 220 ) 221 222 // how [BaseIterator.getErr] updates *errp 223 // - appendErrp replaceErrp 224 type getErrAppend bool 225 226 // getErr reads [BaseIterator.err] and returns err, *errp 227 func (i *BaseIterator[T]) getErr(replace getErrAppend, errp ...*error) (isPresent bool, err error) { 228 var ep = i.err.Load() 229 if isPresent = ep != nil; !isPresent { 230 return // err is not present yet 231 } else if err = *ep; err == nil { 232 return // result is no error 233 } 234 if len(errp) > 0 { 235 if errp0 := errp[0]; errp0 != nil { 236 if replace { 237 *errp0 = err // update errp with error 238 } else { 239 *errp0 = perrors.AppendError(*errp0, err) 240 } 241 } 242 } 243 return // error return 244 } 245 246 // setErr updates atomic error container 247 func (i *BaseIterator[T]) setErr(err error) { i.setErr2(err, panicked) } 248 249 // setErr2 updates atomic error container 250 func (i *BaseIterator[T]) setErr2(err error, state cancelStates) { 251 i.cancelState.Store(uint32(state)) 252 i.err.Store(&err) 253 }