github.com/haraldrudell/parl@v0.4.176/go-result.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  // GoResult makes any number of goroutines awaitable
     9  //   - number of goroutines must be known at time of new
    10  //   - [NewGoResult] is the simplest, goroutines are awaited by [GoResult.ReceiveError]
    11  //   - [NewGoResult2] also has [IsError] method indicating if any goroutine
    12  //     exited with fatal error
    13  //   - [GoResult.IsValid] true if the GoResult is initialized
    14  //   - [GoResult.SendError](errp *error) deferrable, how goroutine sends results
    15  //   - [GoResult.ReceiveError](errp *error, n ...int) (err error)
    16  //   - [GoResult.Count]() (count int) number of buffered errors
    17  //   - [NewGoResult2] also has:
    18  //   - — [GoResult.IsError]() (isError bool) true if any goroutine returned error
    19  //   - — [GoResult.SetIsError]() sets the error flag manually
    20  //   - — [GoResult.Remaining]() (remaining int) number of goroutines that have yet to exit
    21  //   - —
    22  //   - passed by value
    23  //   - getting around that receiver cannot be interface
    24  //   - receiver is value struct with pointer in the form of an interface
    25  type GoResult struct{ goResult }
    26  
    27  func (g GoResult) IsValid() (isValid bool) { return g.goResult != nil }
    28  
    29  type goResult interface {
    30  	// SendError sends error as the final action of a goroutine
    31  	//   - SendError makes a goroutine:
    32  	//   - — awaitable and
    33  	//   - — able to return a fatal error
    34  	//   - — other needs of a goroutine is to initiate and detect cancel and
    35  	//     submit non-fatal errors
    36  	//   - errCh should be a buffered channel large enough for all its goroutines
    37  	//   - — this prevents goroutines from blocking in channel send
    38  	//   - SendError only panics from structural coding problems
    39  	//   - deferrable thread-safe
    40  	SendError(errp *error)
    41  	// ReceiveError is a deferrable function receiving error values from goroutines
    42  	//   - n is number of goroutines to wait for, default 1
    43  	//	- — for [NewGoResult2] default wait for all remaining threads
    44  	//   - errp may be nil
    45  	//   - ReceiveError makes a goroutine:
    46  	//   - — awaitable and
    47  	//   - — able to return a fatal error
    48  	//   - — other needs of a goroutine is to initiate and detect cancel and
    49  	//     submit non-fatal errors
    50  	//   - GoRoutine should have enough capacity for all its goroutines
    51  	//   - — this prevents goroutines from blocking in channel send
    52  	//   - ReceiveError only panics from structural coding problems
    53  	//   - deferrable thread-safe
    54  	ReceiveError(errp *error, n ...int) (err error)
    55  	// Count returns number of results that can be currently collected
    56  	//   - Thread-safe
    57  	Count() (count int)
    58  	// IsError returns if any goroutine has returned an error
    59  	//	- only for [NewGoResult2]
    60  	IsError() (isError bool)
    61  	SetIsError()
    62  	// Remaining returns the number of goroutines that have yet to exit
    63  	//	- only for [NewGoResult2]
    64  	Remaining() (remaining int)
    65  }
    66  
    67  // NewGoResult returns the minimum mechanic to make a goroutine awaitable
    68  //   - n is goroutine capacity, default 1
    69  //   - mechanic is buffered channel
    70  //   - a thread-launcher provides a GoResult value of sufficient capacity to its launched threads
    71  //   - exiting threads send an error value that may be nil
    72  //   - the thread-launcher awaits results one by one
    73  //   - to avoid threads blocking prior to exiting, the channel must have sufficient capacity
    74  //
    75  // Usage:
    76  //
    77  //	func someFunc(text string) (err error) {
    78  //	  var g = parl.NewGoResult()
    79  //	  go goroutine(text, g)
    80  //	  defer g.ReceiveError(&err)
    81  //	  …
    82  //	func goroutine(text string, g parl.GoResult) {
    83  //	  var err error
    84  //	  defer g.SendError(&err)
    85  //	  defer parl.RecoverErr(func() parl.DA { return parl.A() }, &err)
    86  //
    87  //	  err = …
    88  func NewGoResult(n ...int) (goResult GoResult) { return GoResult{goResult: newGoResultChan(n...)} }
    89  
    90  // NewGoResult2 also has [GoResult.IsError] [GoResult.Remaining]
    91  func NewGoResult2(n ...int) (goResult GoResult) {
    92  	return GoResult{goResult: newGoResultStruct(newGoResultChan(n...))}
    93  }