github.com/haraldrudell/parl@v0.4.176/nb-chan-thread.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 import ( 9 "math" 10 11 "github.com/haraldrudell/parl/pruntime" 12 ) 13 14 // sendThread feeds values to the send channel 15 // - may be on-demand or always-on thread 16 // - verbose='NBChan.*sendThread' 17 func (n *NBChan[T]) sendThread(value T, hasValue bool) { 18 var zeroValue T 19 // signal thread exit to CloseNow if it is waiting 20 defer n.tcThreadExitAwaitable.Close() 21 // execute possible deferred close from Close invocation 22 defer n.sendThreadDeferredClose() 23 defer Recover(func() DA { return A() }, nil, n.sendThreadOnError) 24 25 // send value loop 26 for { 27 28 if hasValue { 29 // send the value: blocks here 30 // - until consumer receive, Get, CloseNow or panic 31 // - decrements unsent count 32 n.sendThreadBlockingSend(value) 33 hasValue = false 34 value = zeroValue 35 } 36 37 // obtain next value loop 38 for { 39 40 // check for CloseNow prior to next value 41 if n.sendThreadIsCloseNow() { 42 return // close now exit: immediate discard and exit 43 } 44 45 // if no data, decide on action 46 if n.unsentCount.Load() == 0 { 47 n.sendThreadZero() 48 49 // always-thread not in deferred close: wait for alert 50 if !n.isOnDemandThread.Load() && !n.isCloseInvoked.IsInvoked() { 51 // blocks here 52 if value, hasValue = n.sendThreadWaitForAlert(); hasValue { 53 break // send the value received by alert 54 } 55 continue // re-check closeNow and unsent count for next action 56 57 // on-demand thread or always in deferred close: 58 // exit on no data and no pending sends 59 } else if n.sendThreadExitCheck() { 60 // on-demand thread or always-on after Close exits here 61 return // no data, no pending sends: exit thread 62 } 63 } // obtain next value loop 64 // there is more data to send 65 66 if hasValue { 67 break // send the always-on thread value from alert 68 } 69 // is on-demand thread and data is available 70 71 // wait for [NBChan.gets] to be or reach 0 72 // - Get invocations get items before sendThread 73 if ch := n.getsWait.Ch(); ch != nil { 74 for { 75 select { 76 case <-ch: // Get ceased 77 case n.stateCh() <- NBChanGets: // respond is in Gets wait 78 continue 79 } 80 break 81 } 82 } 83 84 // try to get value from any queue 85 if value, hasValue = n.sendThreadGetNextValue(); hasValue { 86 break // send the value fetched from queues 87 } 88 89 // unsent count has reached zero or Get is in progress 90 // - wait for any sends to conclude that may provide additional items 91 if ch := n.sendsWait.Ch(); ch != nil { 92 for { 93 select { 94 case <-ch: 95 case n.stateCh() <- NBChanSends: 96 continue 97 } 98 break 99 } 100 } 101 } 102 // a value was obtained 103 104 // only send if closeNow has not been invoked 105 if !n.sendThreadNewSendCheck() { 106 return // CloseNow: item is discarded, thread exits 107 } 108 } 109 } 110 111 // sendThreadDeferredClose may close the underlying channel 112 // - is how sendThread executes deferred close 113 // - closes if Close was invoked while thread running and not CloseNow 114 // - invoked by sendThread on exit 115 // - updates dataWaitCh 116 func (n *NBChan[T]) sendThreadDeferredClose() { 117 118 // is it deferred close? 119 if !n.isCloseInvoked.IsInvoked() || // no: Close has not been invoked 120 n.isCloseNow.IsInvoked() { // CloseNow overrides deferred close 121 n.updateDataAvailable() 122 return // no deferred close pending return: noop 123 } 124 125 // for on-demand thread, ensure out of data 126 if n.isOnDemandThread.Load() { 127 128 if n.unsentCount.Load() > 0 { 129 return 130 } 131 // tcThread 132 } 133 134 // execute deferred close 135 // - error is stored in error container. isClosed is active 136 n.executeChClose() 137 // close data waiter 138 n.setDataAvailableAfterClose() 139 } 140 141 // sendThreadZero notifies background that thread 142 // took action on unsent count zero 143 func (n *NBChan[T]) sendThreadZero() { 144 n.tcDoProgressRaised(true) 145 n.tcProgressLock.Lock() 146 defer n.tcProgressLock.Unlock() 147 148 if n.unsentCount.Load() == 0 { 149 n.tcProgressRequired.Store(true) 150 } 151 } 152 153 // sendThreadOnError submits thread panic 154 // - ignores send on closed channel after closenow 155 func (n *NBChan[T]) sendThreadOnError(err error) { 156 if pruntime.IsSendOnClosedChannel(err) && n.isCloseNow.IsInvoked() { 157 return // ignore if the channel was or became closed 158 } 159 n.AddError(err) 160 } 161 162 // sendThreadBlockingSend sends blocking on consumer-receive channel 163 // - decrements unsent count 164 // - blocks until: 165 // - — consumer read the value 166 // - — Get empties the channel using collectSendThreadValue 167 // - — CloseNow discards the value using discardSendThreadValue 168 // - invoked by sendThread holding inputLock 169 func (n *NBChan[T]) sendThreadBlockingSend(value T) { 170 defer n.updateDataAvailable() 171 // count the item just sent — even if panic 172 defer n.unsentCount.Add(math.MaxUint64) 173 // clear two-chan receive second channel 174 n.collectChanActive.Store(nil) 175 // receive value with default has proven to result in default. Therefore: 176 // - two-chan receive is used by tcCollectThreadValue to prevent deadlock and aba 177 // - send-thread provides an atomic true and a nil atomic channel value 178 // upon commencing send operation 179 // - the atomic true allows other threads to write the atomic channel value and 180 // reset the atomic true to false 181 // - a winner thread observing the atomic true value stores a 1-size empty channel, 182 // and proceeds if it is able to set the atomic true value to false 183 // - at end of send operation, send-thread attempts to change the atomic value from true to false 184 // - if the atomic value was true, no two-chan receive is in progress 185 // - otherwise, send-thread sends on the atomic channel 186 // - thereby, send-thread will not enter dead-lock and avoids aba-issue 187 defer n.sendThreadBlockingSendEnd() 188 // tcSendBlock makes collectChanActive available to tcCollectThreadValue threads 189 n.tcSendBlock.Store(true) 190 191 for { 192 select { 193 // send value to consumer or Get 194 // - may block or panic 195 case n.closableChan.Ch() <- value: 196 return 197 case n.stateCh() <- NBChanSendBlock: 198 } 199 } 200 } 201 202 // sendThreadBlockingSendEnd completes any two-chan receive operation 203 func (n *NBChan[T]) sendThreadBlockingSendEnd() { 204 // check if two-chan send was initiated 205 if n.tcSendBlock.CompareAndSwap(true, false) { 206 return // no value collect 207 } 208 209 // send to ensure tcCollectThreadValue is not blocked 210 if cp := n.collectChanActive.Load(); cp != nil { 211 *cp <- struct{}{} 212 } 213 } 214 215 // sendThreadGetNextValue gets the next value for thread 216 // - invoked by [NBChan.sendThread] 217 // - fails if pending Get or unsentCount ends up zero 218 func (n *NBChan[T]) sendThreadGetNextValue() (value T, hasValue bool) { 219 if n.gets.Load() > 0 || n.unsentCount.Load() == 0 { 220 return // send thread suspended by Get return: hasValue: false 221 } 222 // if a thread holding outputLock awaited thread state, 223 // acquiring outputLock here could cause dead-lock 224 // - only Get invocations do this 225 // - therefore, ensure outputLock is not acquired while Get 226 // in progress 227 n.collectorLock.Lock() 228 defer n.collectorLock.Unlock() 229 230 if n.gets.Load() > 0 { 231 return // cancel: Get in progress 232 } 233 n.outputLock.Lock() 234 defer n.outputLock.Unlock() 235 236 if hasValue = len(n.outputQueue) > 0 || n.swapQueues(); !hasValue { 237 return // no value available return: hasValue false 238 } 239 value = n.outputQueue[0] 240 n.outputQueue = n.outputQueue[1:] 241 return // have item return: value: valid, hasValue: true 242 } 243 244 // sendThreadWaitForAlert allows an always-on thread to await alert 245 // - the alert is a two-chan send that may provide a value 246 // - always threads do not exit, instead at end of data 247 // they wait for background events: 248 // - not if didClose 249 // - not if data available 250 // - an alert that may provide a data item 251 func (n *NBChan[T]) sendThreadWaitForAlert() (value T, hasValue bool) { 252 253 // reset atomic channel 254 n.alertChan2Active.Store(nil) 255 // n.threadChWinner true exposes channels to clients 256 n.tcAlertActive.Store(true) 257 // sending on threadCh2 ensures no client is hanging 258 defer n.sendThreadAlertEnd() 259 260 // blocks here 261 // - n.threadCh must be unbuffered for effect to be immediate 262 // - n.threadCh2 is present to prevent client from hanging in threadCh send 263 for { 264 select { 265 // wait for alert 266 case valuep := <-n.alertChan.Get(): 267 if hasValue = valuep != nil; hasValue { 268 value = *valuep 269 n.unsentCount.Add(1) 270 } 271 return 272 // broadcast Alert wait 273 case n.stateCh() <- NBChanAlert: 274 } 275 } 276 } 277 278 func (n *NBChan[T]) sendThreadAlertEnd() { 279 // see if two-chan send operation in progress 280 if n.tcAlertActive.CompareAndSwap(true, false) { 281 return // no 282 } else if cp := n.alertChan2Active.Load(); cp != nil { 283 *cp <- struct{}{} 284 } 285 } 286 287 // sendThreadExitCheck stops thread if inside threadLock, unsentCount is 0 288 // - doStop true: channel has been read to end and no Send SendMany active 289 // - invoked when thread has detected Close invocation and is in deferred close 290 func (n *NBChan[T]) sendThreadExitCheck() (doStop bool) { 291 n.tcThreadLock.Lock() // atomizes unsentCount sends tcRunningThread 292 defer n.tcThreadLock.Unlock() 293 294 if doStop = // 295 n.unsentCount.Load() == 0 && // only stop if out of data 296 n.sends.Load() == 0; // while no sends in progress 297 !doStop { 298 return 299 } 300 301 n.tcRunningThread.Store(false) 302 303 return 304 } 305 306 // sendThreadNewSendCheck ensures a new channel send does not start after 307 // CloseNow 308 // - on closeNow, value is discarded 309 // - re-arms closesOnThreadSend 310 func (n *NBChan[T]) sendThreadNewSendCheck() (doSend bool) { 311 312 if doSend = !n.isCloseNow.IsInvoked(); !doSend { 313 n.unsentCount.Add(math.MaxUint64) // drop the value 314 return // CloseNow inoked return: doSend: false 315 } 316 317 return // doSend: true 318 } 319 320 // sendThreadIsCloseNow checks for CloseNow invocation 321 // - isExit true: CloseNow was invoked 322 func (n *NBChan[T]) sendThreadIsCloseNow() (isExit bool) { 323 if !n.isCloseNow.IsInvoked() { 324 return // no CloseNow invocation return 325 } 326 n.tcThreadLock.Lock() // atomizes CloseNow tcRunningThread 327 defer n.tcThreadLock.Unlock() 328 329 if isExit = n.isCloseNow.IsInvoked(); !isExit { 330 return 331 } 332 n.tcRunningThread.Store(false) 333 return // close now exit 334 }