github.com/haraldrudell/parl@v0.4.176/nb-chan-send.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 "math"
     9  
    10  // Send sends a single value on the channel
    11  //   - non-blocking, thread-safe, panic-free and error-free
    12  //   - if Close or CloseNow was invoked, items are discarded
    13  func (n *NBChan[T]) Send(value T) {
    14  	if n.isCloseInvoked.IsInvoked() {
    15  		return // no send after Close(), atomic performance: noop
    16  	}
    17  	n.preSend()
    18  	n.inputLock.Lock()
    19  	defer n.postSend()
    20  
    21  	// if Close or CloseNow was invoked, items are discarded
    22  	if n.isCloseInvoked.IsInvoked() {
    23  		return // no send after Close() return: noop
    24  	}
    25  
    26  	// try providing value to thread
    27  	//	- ensures a thread is running if configured
    28  	//	- updates threadProgressRequired
    29  	if n.tcAlertOrLaunchThreadWithValue(value) {
    30  		return // value was provided to a thread
    31  	}
    32  
    33  	// save value in [NBChan.inputQueue]
    34  	if n.inputQueue == nil {
    35  		n.inputQueue = n.newQueue(1) // will allocation proper size
    36  	}
    37  	n.inputQueue = append(n.inputQueue, value)
    38  	n.inputCapacity.Store(uint64(cap(n.inputQueue)))
    39  	n.tcAddProgress(1)
    40  }
    41  
    42  // Send sends many values non-blocking, thread-safe, panic-free and error-free on the channel
    43  //   - if values is length 0 or nil, SendMany only returns count and capacity
    44  func (n *NBChan[T]) SendMany(values []T) {
    45  	var valueCount = len(values)
    46  	if n.isCloseInvoked.IsInvoked() || valueCount == 0 {
    47  		return // no send after Close(), atomic performance: noop
    48  	}
    49  	n.preSend()
    50  	n.inputLock.Lock()
    51  	defer n.postSend()
    52  
    53  	if n.isCloseInvoked.IsInvoked() {
    54  		return // no send after Close() return: noop
    55  	}
    56  
    57  	if n.tcAlertOrLaunchThreadWithValue(values[0]) {
    58  		values = values[1:]
    59  		valueCount--
    60  		if valueCount == 0 {
    61  			return // one value handed to thread return: complete
    62  		}
    63  	}
    64  
    65  	// save values in [NBChan.inputQueue]
    66  	if n.inputQueue == nil {
    67  		n.inputQueue = n.newQueue(valueCount)
    68  	}
    69  	n.inputQueue = append(n.inputQueue, values...)
    70  	n.inputCapacity.Store(uint64(cap(n.inputQueue)))
    71  	n.tcAddProgress(valueCount)
    72  }
    73  
    74  // preSend registers a Send or SendMany invocation pre-inputLock
    75  //   - send count is in [NBChan.sends]
    76  //   - handles [NBChan.sendsWait] that prevents a thread from exiting
    77  //     during Send SendMany invocations
    78  func (n *NBChan[T]) preSend() {
    79  	if n.sends.Add(1) == 1 {
    80  		n.sendsWait.HoldWaiters()
    81  	}
    82  }
    83  
    84  // post send is deferred for [NBChan.Send] and [NBChan.SendMany]
    85  //   - release inputLock
    86  //   - alert thread if no pending Get and values ar present
    87  func (n *NBChan[T]) postSend() {
    88  	n.inputLock.Unlock()
    89  
    90  	// update dataWaitCh
    91  	n.updateDataAvailable()
    92  
    93  	// decrement sends
    94  	if n.sends.Add(math.MaxUint64) == 0 {
    95  		n.sendsWait.ReleaseWaiters()
    96  	}
    97  
    98  	// ensure progress
    99  	for {
   100  		if isZeroObserved, isGets := n.tcIsDeferredSend(); !isZeroObserved || isGets {
   101  			// progress not required or
   102  			// deferred by Get invocations
   103  			return
   104  		} else if !n.tcAwaitProgress() {
   105  			// progress was secured
   106  			return
   107  		} else if n.gets.Load() > 0 {
   108  			// subsequent Send SendMany exist
   109  			//	- after sends decrement, those will arrive at ensure progress
   110  			return
   111  		}
   112  	}
   113  }
   114  
   115  // ensureInput allocates or enlarges for [NBChan.SetAllocationSize]
   116  func (n *NBChan[T]) ensureInput(size int) (queue []T) {
   117  	n.inputLock.Lock()
   118  	defer n.inputLock.Unlock()
   119  
   120  	if n.inputQueue != nil {
   121  		return
   122  	}
   123  	n.inputQueue = n.newQueue(size)
   124  	return
   125  }
   126  
   127  // newQueue allocates a new queue slice
   128  //   - capacity is at least count elements
   129  //   - the slice is empty
   130  func (n *NBChan[T]) newQueue(count int) (queue []T) {
   131  
   132  	// determine size
   133  	var size = int(n.allocationSize.Load())
   134  	if size > 0 {
   135  		if count > size {
   136  			size = count
   137  		}
   138  	} else {
   139  		size = defaultNBChanSize
   140  		if count > size {
   141  			size = count * 2
   142  		}
   143  	}
   144  
   145  	// return allocated zero-length queue
   146  	return make([]T, size)[:0]
   147  }