diff --git a/concurrency/waitgroup/error_handling.go b/concurrency/waitgroup/error_handling.go new file mode 100644 index 0000000..bf8085c --- /dev/null +++ b/concurrency/waitgroup/error_handling.go @@ -0,0 +1,78 @@ +package main + +import "fmt" +import "sync" +import "time" + +type Error struct { + message string +} + +func (e Error) Error() string { + return e.message +} + +func syncWaitWithErrorHandling() { + var wg sync.WaitGroup + waitGroupLength := 8 + errChannel := make(chan error, 1) + + // Setup waitgroup to match the number of go routines we'll launch off + wg.Add(waitGroupLength) + finished := make(chan bool, 1) // this along with wg.Wait() are why the error handling works and doesn't deadlock + + for i := 0; i < waitGroupLength; i++ { + + go func(i int) { + fmt.Printf("Go routine %d executed\n", i+1) + + // Sleep for the time needed for each other go routine to complete. + // This helps show that the program exists with the last go routine to fail. + // comment this line if you want to see it fail fast + time.Sleep(time.Duration(waitGroupLength - i)) + + time.Sleep(0) // only here so the time import is needed + + // comment out the following 3 lines to see what happens without an error + // Note, the channel has a length of one so the last go routine to error + // will always be the last error. + + if i%4 == 1 { + errChannel <- Error{fmt.Sprintf("Errored on routine %d", i+1)} + } + + // Mark the wait group as Done so it does not hang + wg.Done() + }(i) + } + + // Put the wait group in a go routine. + // By putting the wait group in the go routine we ensure either all pass + // and we close the "finished" channel or we wait forever for the wait group + // to finish. + // + // Waiting forever is okay because of the blocking select below. + go func() { + wg.Wait() + close(finished) + }() + + // This select will block until one of the two channels returns a value. + // This means on the first failure in the go routines above the errChannel will release a + // value first. Because there is a "return" statement in the err check this function will + // exit when an error occurs. + // + // Due to the blocking on wg.Wait() the finished channel will not get a value unless all + // the go routines before were successful because not all the wg.Done() calls would have + // happened. + select { + case <-finished: + case err := <-errChannel: + if err != nil { + fmt.Println("error ", err) + return + } + } + + fmt.Println("Successfully executed all go routines") +} diff --git a/playground/main.go b/playground/main.go index 6bbadc0..596c70c 100644 --- a/playground/main.go +++ b/playground/main.go @@ -1,9 +1,15 @@ package main -func main() { +import "github.com/davecgh/go-spew/spew" +func main() { + t1 := make(DogsList, 0) + t2 := DogList{} + spew.Dump(t1, t2) } +type DogsList []*Dog + type Dog struct { Name string }