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 }