✨ Implement waiterr.WaitForError
This commit is contained in:
parent
57e01555a9
commit
2270f4c795
1 changed files with 55 additions and 8 deletions
63
waiterr.go
63
waiterr.go
|
|
@ -7,16 +7,38 @@ import (
|
||||||
|
|
||||||
// WaitErr wraps a sync.WaitGroup with error handling.
|
// WaitErr wraps a sync.WaitGroup with error handling.
|
||||||
type WaitErr struct {
|
type WaitErr struct {
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
errs []error
|
errs []error
|
||||||
mut sync.RWMutex
|
mut sync.RWMutex
|
||||||
|
firstErr error
|
||||||
|
firstErrOnce sync.Once
|
||||||
|
errCh chan error // Buffered channel of size 1
|
||||||
|
initErrChOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go runs f in its own goroutine. When f returns, its error is stored, and returned
|
// Go runs f in its own goroutine. When f returns, its error is stored, and returned
|
||||||
// with we.Wait()
|
// with we.Wait().
|
||||||
func (we *WaitErr) Go(f func() error) {
|
func (we *WaitErr) Go(f func() error) {
|
||||||
|
we.initErrChOnce.Do(func() {
|
||||||
|
we.errCh = make(chan error, 1)
|
||||||
|
})
|
||||||
wrap := func() {
|
wrap := func() {
|
||||||
err := f()
|
err := f()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
we.firstErrOnce.Do(func() {
|
||||||
|
we.mut.Lock() // Acquire lock before writing to firstErr
|
||||||
|
we.firstErr = err
|
||||||
|
we.mut.Unlock() // Release lock after writing
|
||||||
|
|
||||||
|
// Non-blocking send to errCh
|
||||||
|
select {
|
||||||
|
case we.errCh <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
we.mut.Lock()
|
we.mut.Lock()
|
||||||
defer we.mut.Unlock()
|
defer we.mut.Unlock()
|
||||||
we.errs = append(we.errs, err)
|
we.errs = append(we.errs, err)
|
||||||
|
|
@ -27,10 +49,35 @@ func (we *WaitErr) Go(f func() error) {
|
||||||
// WaitForError waits for the first error to be returned by one of our go routines, and immediately returns
|
// WaitForError waits for the first error to be returned by one of our go routines, and immediately returns
|
||||||
// with that error. If all functions return successfully, a nil is returned.
|
// with that error. If all functions return successfully, a nil is returned.
|
||||||
func (we *WaitErr) WaitForError() error {
|
func (we *WaitErr) WaitForError() error {
|
||||||
// Implement this
|
if we.errCh == nil {
|
||||||
// If we already have an error, return it immediately without waiting
|
panic("WaitForError called before Go, errCh is nil")
|
||||||
// If no error has yet been returned, wait for the very first error and return it
|
}
|
||||||
return nil
|
// Check if an error has already been set
|
||||||
|
we.mut.RLock()
|
||||||
|
if we.firstErr != nil {
|
||||||
|
err := we.firstErr
|
||||||
|
we.mut.RUnlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
we.mut.RUnlock()
|
||||||
|
|
||||||
|
// Create a channel to signal when all goroutines are done
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
we.wg.Wait()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-we.errCh:
|
||||||
|
return err
|
||||||
|
case <-done:
|
||||||
|
// All goroutines finished, and no error was sent to errCh
|
||||||
|
// Re-check firstErr in case it was set just before 'done' was closed
|
||||||
|
we.mut.RLock()
|
||||||
|
defer we.mut.RUnlock()
|
||||||
|
return we.firstErr // This will be nil if no error occurred
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all current goroutines to finish. Return an error that combines all errors returned
|
// Wait for all current goroutines to finish. Return an error that combines all errors returned
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue