github.com/haraldrudell/parl@v0.4.176/nb-chan-thread-control.go (about) 1 /* 2 © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package parl 7 8 const ( 9 // tcStartThread 10 NoValue = false 11 // tcStartThread 12 HasValue = true 13 ) 14 15 // tcCreateWinner seeks permission to create sendThread 16 // - isWinner true: go n.sendThread should be invoked by this invocation 17 // - isWinner false: a thread is running, being started or it is Close/CloseNow 18 func (n *NBChan[T]) tcCreateWinner() (isWinner bool) { 19 n.tcThreadLock.Lock() // atomizes Close/CloseNow tcRunningThread 20 defer n.tcThreadLock.Unlock() 21 22 // no thread creation after CloseNow 23 if n.isCloseNow.IsInvoked() { 24 return 25 // no thread creation after Close if channel object empty 26 } else if n.isCloseInvoked.IsInvoked() && n.unsentCount.Load() == 0 { 27 return 28 } 29 30 // note that thread did at one time launch 31 // - before tcRunningThread is set to true 32 n.tcDidLaunchThread.CompareAndSwap(false, true) 33 34 // check for thread already running or this invocation should not launch it 35 if isWinner = n.tcRunningThread.CompareAndSwap(false, true); !isWinner { 36 return // thread was already running return 37 } 38 39 // arm thread exit awaitable 40 n.tcThreadExitAwaitable.Open() 41 42 return 43 } 44 45 // tcAlertOrLaunchThreadWithValue ensures thread progress from a Send endMany during unsent count zero 46 // - didProvideValue true: value was provided to thread via launch or alert and 47 // unsent count was incremented 48 // - if on-demand or always thread and a unsent count zero, thread progress must be guaranteed 49 // - thread progress is deferred if Get is in progress 50 // - Invoked by [NBChan.Send] and [NBChan.SendMany] 51 // - on invocation, value is not part of unsentCount 52 func (n *NBChan[T]) tcAlertOrLaunchThreadWithValue(value T) (didProvideValue bool) { 53 54 // if unsent count is zero, a thread may have to be alerted or launched 55 // - no thread may have been launched 56 // - on-demand thread may have exited on unsent count zero 57 // - always thread may await an alert 58 // - no action is necessary if: 59 // - — threading is not used 60 // - — unsent count is not zero 61 // - — Gets are in progress for which a thread would be a detour 62 // - NBChan may be configured for no-thread on-demand-thread or always-on thread 63 // - no thread may be running 64 // - this is the only Send SendMany invocation 65 // - Get invocations may be ongoing 66 67 // if NBChan is configured for no thread, value cannot be provided 68 if n.isNoThread.Load() { 69 return // no-thread configuration 70 } 71 72 // if unsent count is not zero, no change is required to threading 73 // - only Send SendMany which use inputLock can increase this value 74 if n.unsentCount.Load() > 0 { 75 return // channel is not empty return 76 } 77 78 // - unsent count is zero 79 // - configuration is on-demand-thread or always-thread 80 // - there may be no thread running 81 // - progress must now be guaranteed by: 82 // - — launching the send-thread 83 // - — alerting an always-thread 84 // - — for on-demand thread in unknown state, 85 // observing it in a state once data has been added to inputBuffer. 86 // It may otherwise exit 87 88 // if Get is in progress, thread should launch later 89 if _, isGets := n.tcIsDeferredSend(); isGets { 90 return // deferred: threadProgressRequired true 91 } 92 93 // starting the thread, that is most important 94 // - a launched thread will go to send value 95 if _, didProvideValue = n.tcCreateProgress(); didProvideValue { 96 n.unsentCount.Add(1) 97 go n.sendThread(value, HasValue) 98 return // thread was started with value 99 } 100 101 // try alerting a waiting always-on thread without waiting 102 // - if successful, thread will go to send value 103 if didProvideValue = n.tcAlertProgress(&value); didProvideValue { 104 return // always-on thread alerted with value 105 } 106 107 // there is a thread running without holding an item 108 // - on-demand or always 109 // - may be executing towards: exit getsWait sendsWait alert 110 // - Close or CloseNow may have been invoked 111 // - at end of Send SendMany Get invocations, a static thread-state must be awaited 112 // so that progress is guaranteed 113 // - issue is that: 114 // - — an on-demand thread may exit while items are present 115 // - — an always thread may enter alert state where it must be alerted 116 // - flag it to be dealt with once all Send SendMany Get completes 117 return 118 } 119 120 // tcCreateProgress seeks progress by creating the send-thread 121 // - honorProgressRaised true: isProgress is only true if tcProgressRaised remains false 122 // - isProgress true: this operation is thread progress, isCreateThread is also true 123 // - isCreateThread true: caller should invoke go n.sendThread 124 func (n *NBChan[T]) tcCreateProgress(honorProgressRaised ...bool) (isProgress, isCreateThread bool) { 125 n.tcProgressLock.Lock() 126 defer n.tcProgressLock.Unlock() 127 128 if isCreateThread = n.tcCreateWinner(); !isCreateThread { 129 return // no progress no thread to be created 130 } else if len(honorProgressRaised) > 0 && honorProgressRaised[0] && n.tcDoProgressRaised() { 131 return // progressRaised true, so this is not progress 132 } 133 isProgress = true 134 return 135 } 136 137 // HonorProgressRaised ignore any progress made if tcProgressRaised is true 138 const HonorProgressRaised = true 139 140 // record progress regardless of tcProgressRaised 141 const IgnoreProgressRaised = false 142 143 // tcAlertProgress attempts progress via alert ignoring tcProgressRaised 144 // - valuep: if present and non-nil provided in alert 145 // - isProgress true: value was provided, operation was thread progress 146 func (n *NBChan[T]) tcAlertProgress(valuep ...*T) (isProgress bool) { 147 isProgress, _ = n.tcAlertProgress2(IgnoreProgressRaised, valuep...) 148 return 149 } 150 151 // tcAlertProgress2 attempts progress via alert optionally honoring tcProgressRaised 152 // - honorProgressRaised true: isProgress is only true if tcProgressRaised remains false 153 // - valuep: optional value provided with alert 154 // - isProgress operation counts as progress 155 // - didAlert an alert was successfully sent, if a value was present is was provided 156 // - sendThread upon receiving the value, increases unsent count 157 func (n *NBChan[T]) tcAlertProgress2(honorProgressRaised bool, valuep ...*T) (isProgress, didAlert bool) { 158 n.tcProgressLock.Lock() 159 defer n.tcProgressLock.Unlock() 160 161 // try alerting the thread 162 var valuep0 *T 163 if len(valuep) > 0 { 164 valuep0 = valuep[0] 165 } 166 if didAlert = // 167 n.tcAlertActive.Load() && // only when send-thread awaits alert 168 n.tcAlertThread(valuep0); // 169 !didAlert { 170 return // no successful alert 171 } else if isProgress = !honorProgressRaised || !n.tcDoProgressRaised(); !isProgress { 172 return // was progress raised true 173 } 174 n.tcProgressRequired.Store(false) 175 return 176 } 177 178 func (n *NBChan[T]) tcAddProgress(count int) { 179 n.tcDoProgressRaised(true) 180 n.tcProgressLock.Lock() 181 defer n.tcProgressLock.Unlock() 182 183 if n.unsentCount.Add(uint64(count)) == uint64(count) && !n.isNoThread.Load() { 184 n.tcProgressRequired.Store(true) 185 } 186 } 187 188 // tcAwaitProgress awaits a static state from thread then ensures progress 189 // - threadProgressRequired true: progress is currently not guaranteed 190 // - progress is: 191 // - — creating sendThread 192 // - — alerting sendThread 193 // - — a non-exit thread-state observed 194 // - if tcProgressRequired was true on invocation and a zero-count event occured during wait, 195 // threadProgressRequired is true 196 func (n *NBChan[T]) tcAwaitProgress() (threadProgressRequired bool) { 197 n.tcAwaitProgressLock.Lock() // ensures critical section 198 defer n.tcAwaitProgressLock.Unlock() 199 200 // check if progress action remains required 201 if threadProgressRequired = n.tcProgressRequired.Load(); !threadProgressRequired { 202 return 203 } 204 // reset progress raised, ie. 205 // - thread observing unsent count zero 206 // - Send SendMany increasing unsent count from zero 207 n.tcDoProgressRaised(false) 208 209 // await static thread status 210 // - cannot hold tcProgressLock lock 211 var threadState NBChanTState 212 select { 213 // thread exit 214 case <-n.tcThreadExitAwaitable.Ch(): 215 if n.isOnDemandThread.Load() { 216 var isProgress, isCreateThread = n.tcCreateProgress(HonorProgressRaised) 217 if isCreateThread { 218 // start thread without value 219 var value T 220 go n.sendThread(value, NoValue) 221 } 222 threadProgressRequired = !isProgress 223 } 224 return 225 case threadState = <-n.stateCh(): 226 } 227 // received thread status 228 229 // alert 230 if threadState == NBChanAlert { 231 if isProgress, _ := n.tcAlertProgress2(HonorProgressRaised); isProgress { 232 threadProgressRequired = false 233 } 234 return 235 } 236 237 // all other states are good states 238 // - if a zero unsent period occurred in the meantime, 239 // - tcProgressRaised is true and 240 // - the operation was not progress 241 threadProgressRequired = n.tcDoProgressRaised() 242 243 return 244 } 245 246 // tcDoProgressRaised updates and or returns tcProgressRaised 247 // - wasRaised is the tcProgressRaised at time of invocation 248 // - isRaised missing: read operation, otherwise tcProgressRaised is set to isRaised 249 // - tcProgressRaised is set upon: 250 // on Send SendMany adding from unsent count zero 251 // - — the send-thread taking action on unsent count zero or 252 // - — Send SendMany adding items from unsent count zero 253 // - it indicates that sendThread progress may fail if not ensured 254 // - tcProgressRaised is used when awaiting thread-state since tcProgressLock 255 // cannot be held 256 // - if a zero condition occured during await of thread-state, the wait operation must be retried 257 func (n *NBChan[T]) tcDoProgressRaised(isRaised ...bool) (wasRaised bool) { 258 if len(isRaised) == 0 { 259 wasRaised = n.tcProgressRaised.Load() 260 return 261 } 262 wasRaised = n.tcProgressRaised.Swap(isRaised[0]) 263 return 264 } 265 266 // tcAlertThread alerts any waiting always-thread 267 // - invoked from Send/SendMany 268 // - value has not been added to unsentCount yet 269 // - increments unsentCount if value is non-nil and was provided to thread 270 func (n *NBChan[T]) tcAlertThread(valuep ...*T) (didAlert bool) { 271 // atomizes always-alert with detecting no Get in progress 272 n.collectorLock.Lock() 273 defer n.collectorLock.Unlock() 274 275 // filter send request 276 if n.gets.Load() > 0 || // not when no Get in progress 277 !n.tcAlertActive.Load() { // only when send-thread awaits alert 278 return // no alert now 279 } 280 281 // prepare two-chan send second channel 282 var alertChan2 = n.alertChan2.Get(1) 283 if len(alertChan2) > 0 { 284 <-alertChan2 285 } 286 n.alertChan2Active.Store(&alertChan2) 287 288 // verify that two-chan send still available 289 if !n.tcAlertActive.CompareAndSwap(true, false) { 290 return 291 } 292 293 // send value to always-thread 294 // - always thread increments unsentCount on reception 295 var valuep0 *T 296 if len(valuep) > 0 { 297 valuep0 = valuep[0] 298 } 299 select { 300 case n.alertChan.Get() <- valuep0: 301 didAlert = true 302 case <-alertChan2: 303 } 304 return 305 } 306 307 // tcIsDeferredSend checks for a progress requirement 308 // - invoked by the last ending Get invocation 309 func (n *NBChan[T]) tcIsDeferredSend() (isProgressRequired, isGets bool) { 310 n.tcGetProgressLock.Lock() 311 defer n.tcGetProgressLock.Unlock() 312 313 isProgressRequired = n.tcProgressRequired.Load() 314 isGets = n.gets.Load() > 0 315 316 return 317 } 318 319 // tcCollectThreadValue receives any value in sendThread channel send 320 // - invoked by [NBChan.Get] while holding output lock 321 // - must await any thread value to ensure values provided in order 322 // - thread receives value from: 323 // - — Send SendMany that launches thread, but only when sent count 0 324 // - — always: thread alert 325 // - —on-demand: GetNextValue 326 func (n *NBChan[T]) tcCollectThreadValue() (value T, hasValue bool) { 327 328 // if thread is not running, it does not hold data 329 if !n.tcRunningThread.Load() { 330 return // thread not running 331 } 332 333 // await static thread state 334 // - state must be awaited since the thread may be in progress 335 // with a value towards NBChanSendBlock 336 // - if NBChanSendBlock, threads hold a value 337 // - in all other static thread states, thread holds no value 338 // - because this thread holds outputLock, 339 // on-demand thread cannot collect additional values 340 // - collectorLock ensures that no always-thread alerts are carried out 341 // while Get is in progress 342 select { 343 // thread exited 344 case <-n.tcThreadExitAwaitable.Ch(): 345 return // thread exited return 346 case chanState := <-n.stateCh(): 347 // if it is not send value block, ignore 348 // - NBChanSendBlock is the only wait where thread has value 349 if chanState != NBChanSendBlock { 350 return // thread is not held in send value 351 } 352 } 353 // thread holds value in state NBChanSendBlock 354 355 // because this thread holds outputLock, 356 // only one thread at a time may arrive here 357 // - competing with consumers and closeNow for the value 358 359 // ensure two-chan receive operation is available 360 if !n.tcSendBlock.Load() { 361 return // the value went to another thread 362 } 363 364 // prepare two-chan receive second channel 365 var collectChan = n.collectChan.Get(1) 366 if len(collectChan) > 0 { 367 <-collectChan 368 } 369 n.collectChanActive.Store(&collectChan) 370 371 // seek permission for two-chan receive 372 if !n.tcSendBlock.CompareAndSwap(true, false) { 373 return // the value went to another thread 374 } 375 376 // two-chan fetch of value 377 select { 378 case value, hasValue = <-n.closableChan.Ch(): 379 case <-collectChan: 380 } 381 382 return 383 } 384 385 // returns a channel producing values if thread is holding: 386 // - NBChanSendBlock NBChanAlert NBChanGets NBChanSends 387 func (n *NBChan[T]) stateCh() (ch chan NBChanTState) { 388 if chp := n.tcState.Load(); chp != nil { 389 ch = *chp 390 return 391 } 392 ch = make(chan NBChanTState) 393 if n.tcState.CompareAndSwap(nil, &ch) { 394 return 395 } 396 ch = *n.tcState.Load() 397 return 398 }