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  }