github.com/haraldrudell/parl@v0.4.176/close-channel.go (about)

     1  /*
     2  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package parl
     7  
     8  import (
     9  	"github.com/haraldrudell/parl/perrors"
    10  	"github.com/haraldrudell/parl/pruntime"
    11  )
    12  
    13  const (
    14  	CloseChannelDrain = true
    15  )
    16  
    17  // CloseChannel closes a channel
    18  //   - CloseChannel is thread-safe, deferrable and panic-free,
    19  //     handles closed-channel panic, nil-channel case and
    20  //     has channel drain feature
    21  //   - isNilChannel returns true if ch is nil.
    22  //     closing a nil channel would cause panic.
    23  //   - isCloseOfClosedChannel is true if close paniced due to
    24  //     the channel already closed.
    25  //     A channel transferring data cannot be inspected for being
    26  //     closed
    27  //   - if errp is non-nil, panic values updates it using errors.AppendError.
    28  //   - if doDrain is [parl.CloseChannelDrain], the channel is drained first.
    29  //     Note: closing a channel while a thread is blocked in channel send is
    30  //     a data race.
    31  //     If a thread is continuously sending items and doDrain is true,
    32  //     CloseChannel will block indefinitely.
    33  //   - n returns the number of drained items.
    34  func CloseChannel[T any](ch chan T, errp *error, drainChannel ...bool) (
    35  	isNilChannel, isCloseOfClosedChannel bool, n int, err error,
    36  ) {
    37  
    38  	// nil channel case
    39  	if isNilChannel = ch == nil; isNilChannel {
    40  		return // closing of nil channel return
    41  	}
    42  
    43  	// channel drain feature
    44  	if len(drainChannel) > 0 && drainChannel[0] {
    45  		for {
    46  			select {
    47  			// read non-blocking from the channel
    48  			//	- ok true: received item, channel is not closed
    49  			//	- ok false: channel is closed
    50  			case _, ok := <-ch:
    51  				if ok {
    52  					// the channel is not closed
    53  					n++
    54  					continue // read next item
    55  				}
    56  			default: // channel is open but has no items
    57  			}
    58  			break // closed or no items
    59  		}
    60  	}
    61  
    62  	// close channel
    63  	if Closer(ch, &err); err == nil {
    64  		return // close successful return
    65  	}
    66  
    67  	// handle close error
    68  	isCloseOfClosedChannel = pruntime.IsCloseOfClosedChannel(err)
    69  	if errp != nil {
    70  		*errp = perrors.AppendError(*errp, err)
    71  	}
    72  
    73  	return
    74  }