github.com/haraldrudell/parl@v0.4.176/go-result-struct.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  	"sync/atomic"
    11  
    12  	"github.com/haraldrudell/parl/perrors"
    13  )
    14  
    15  // when when GoResult.g is has multiple values
    16  type goResultStruct struct {
    17  	goResultChan
    18  	remaining atomic.Uint64
    19  	isError   atomic.Bool
    20  }
    21  
    22  // NewGoResult2 also has isError
    23  func newGoResultStruct(ch goResultChan) (goResult *goResultStruct) {
    24  	g := goResultStruct{goResultChan: ch}
    25  	g.remaining.Store(uint64(cap(ch)))
    26  	return &g
    27  }
    28  
    29  // ReceiveError is a deferrable function receiving error values from goroutines
    30  //   - n is number of goroutines to wait for, default 1
    31  //   - errp may be nil
    32  //   - ReceiveError makes a goroutine:
    33  //   - — awaitable and
    34  //   - — able to return a fatal error
    35  //   - — other needs of a goroutine is to initiate and detect cancel and
    36  //     submit non-fatal errors
    37  //   - GoRoutine should have enough capacity for all its goroutines
    38  //   - — this prevents goroutines from blocking in channel send
    39  //   - ReceiveError only panics from structural coding problems
    40  //   - deferrable thread-safe
    41  func (g *goResultStruct) ReceiveError(errp *error, n ...int) (err error) {
    42  	var remainingErrors int
    43  	if len(n) > 0 {
    44  		remainingErrors = n[0]
    45  	} else {
    46  		remainingErrors = int(g.remaining.Load())
    47  	}
    48  
    49  	// await goroutine results
    50  	for ; remainingErrors > 0; remainingErrors-- {
    51  
    52  		// blocks here
    53  		//	- wait for a result from a goroutine
    54  		var e = <-g.goResultChan
    55  		if g.remaining.Add(math.MaxUint64) == 0 {
    56  			break // end of configured errors
    57  		} else if e == nil {
    58  			continue // good return: ignore
    59  		}
    60  
    61  		// goroutine exited with error
    62  		if !g.isError.Load() {
    63  			g.isError.Store(true)
    64  		}
    65  		// ensure e has stack
    66  		e = perrors.Stack(e)
    67  		// build error list
    68  		err = perrors.AppendError(err, e)
    69  	}
    70  
    71  	// final action: update errp if present
    72  	if err != nil && errp != nil {
    73  		*errp = perrors.AppendError(*errp, err)
    74  	}
    75  
    76  	return
    77  }
    78  
    79  func (g *goResultStruct) SetIsError() {
    80  	if g.isError.Load() {
    81  		return
    82  	}
    83  	g.isError.Store(true)
    84  }
    85  
    86  // IsError returns if any goroutine has returned an error
    87  func (g *goResultStruct) IsError() (isError bool) { return g.isError.Load() }
    88  
    89  // Remaining returns the number of goroutines that have yet to exit
    90  func (g *goResultStruct) Remaining() (remaining int) { return int(g.remaining.Load()) }