From 2270f4c795152f2466d9a695ae16ce2c10c2569b Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Thu, 13 Nov 2025 14:44:07 -0600 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Implement=20waiterr.WaitForError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- waiterr.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/waiterr.go b/waiterr.go index 3da73bf..9e21c26 100644 --- a/waiterr.go +++ b/waiterr.go @@ -7,16 +7,38 @@ import ( // WaitErr wraps a sync.WaitGroup with error handling. type WaitErr struct { - wg sync.WaitGroup - errs []error - mut sync.RWMutex + wg sync.WaitGroup + errs []error + 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 -// with we.Wait() +// with we.Wait(). func (we *WaitErr) Go(f func() error) { + we.initErrChOnce.Do(func() { + we.errCh = make(chan error, 1) + }) wrap := func() { 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() defer we.mut.Unlock() 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 // with that error. If all functions return successfully, a nil is returned. func (we *WaitErr) WaitForError() error { - // Implement this - // If we already have an error, return it immediately without waiting - // If no error has yet been returned, wait for the very first error and return it - return nil + if we.errCh == nil { + panic("WaitForError called before Go, errCh is 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