github.com/haraldrudell/parl@v0.4.176/go-result-chan.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  	"github.com/haraldrudell/parl/perrors"
    10  )
    11  
    12  const (
    13  	// minimum error channel length
    14  	minGoResultLength = 1
    15  	// default number of errors to receive
    16  	defaultReceive = 1
    17  )
    18  
    19  // when GoResult.g is error channel only
    20  type goResultChan chan error
    21  
    22  func newGoResultChan(n ...int) (g goResultChan) {
    23  	var n0 int
    24  	if len(n) > 0 {
    25  		n0 = n[0]
    26  	}
    27  	if n0 < minGoResultLength {
    28  		n0 = minGoResultLength
    29  	}
    30  	return make(chan error, n0)
    31  }
    32  
    33  // SendError sends error as the final action of a goroutine
    34  //   - SendError makes a goroutine:
    35  //   - — awaitable and
    36  //   - — able to return a fatal error
    37  //   - — other needs of a goroutine is to initiate and detect cancel and
    38  //     submit non-fatal errors
    39  //   - errCh should be a buffered channel large enough for all its goroutines
    40  //   - — this prevents goroutines from blocking in channel send
    41  //   - SendError only panics from structural coding problems
    42  //   - deferrable thread-safe
    43  func (g goResultChan) SendError(errp *error) {
    44  	if errp == nil {
    45  		panic(NilError("errp"))
    46  	}
    47  
    48  	didSend, isNilChannel, isClosedChannel, err := ChannelSend(g, *errp, SendNonBlocking)
    49  	if didSend {
    50  		return // error value sent return
    51  	} else if isNilChannel {
    52  		err = perrors.ErrorfPF("fatal: error channel nil: %w", err)
    53  	} else if isClosedChannel {
    54  		err = perrors.ErrorfPF("fatal: error channel closed: %w", err)
    55  	} else if err != nil {
    56  		err = perrors.ErrorfPF("fatal: panic when sending on error channel: %w", err)
    57  	} else {
    58  		err = perrors.NewPF("fatal: error channel blocking on send")
    59  	}
    60  	panic(err)
    61  }
    62  
    63  // ReceiveError is a deferrable function receiving error values from goroutines
    64  //   - n is number of goroutines to wait for, default 1
    65  //   - errp may be nil
    66  //   - ReceiveError makes a goroutine:
    67  //   - — awaitable and
    68  //   - — able to return a fatal error
    69  //   - — other needs of a goroutine is to initiate and detect cancel and
    70  //     submit non-fatal errors
    71  //   - GoRoutine should have enough capacity for all its goroutines
    72  //   - — this prevents goroutines from blocking in channel send
    73  //   - ReceiveError only panics from structural coding problems
    74  //   - deferrable thread-safe
    75  func (g goResultChan) ReceiveError(errp *error, n ...int) (err error) {
    76  	var remainingErrors int
    77  	if len(n) > 0 {
    78  		remainingErrors = n[0]
    79  	} else {
    80  		remainingErrors = defaultReceive
    81  	}
    82  
    83  	// await goroutine results
    84  	for ; remainingErrors > 0; remainingErrors-- {
    85  
    86  		// blocks here
    87  		//	- wait for a result from a goroutine
    88  		var e = <-g
    89  		if e == nil {
    90  			continue // good return: ignore
    91  		}
    92  
    93  		// goroutine exited with error
    94  		// ensure e has stack
    95  		e = perrors.Stack(e)
    96  		// build error list
    97  		err = perrors.AppendError(err, e)
    98  	}
    99  
   100  	// final action: update errp if present
   101  	if err != nil && errp != nil {
   102  		*errp = perrors.AppendError(*errp, err)
   103  	}
   104  
   105  	return
   106  }
   107  
   108  // Count returns number of results that can be currently collected
   109  //   - Thread-safe
   110  func (g goResultChan) Count() (count int) { return len(g) }
   111  
   112  func (g goResultChan) SetIsError() {
   113  	panic(perrors.NewPF("NewGoResult does not provide SetIsError: use NewGoResult2"))
   114  }
   115  
   116  func (g goResultChan) IsError() (isError bool) {
   117  	panic(perrors.NewPF("NewGoResult does not provide IsError: use NewGoResult2"))
   118  }
   119  
   120  func (g goResultChan) Remaining() (remaining int) {
   121  	panic(perrors.NewPF("NewGoResult does not provide Remaining: use NewGoResult2"))
   122  }