Close multiple goroutine if an error occurs in one in go

Viewed 16536

consider this function :

func doAllWork() error {

    var wg sync.WaitGroup

    for i := 0; i < 2; i++ {

        wg.add(1)
        go func() {

            defer wg.Done()
            for j := 0; j < 10; j++ {
                result, err := work(j)
                if err != nil {
                    // can't use `return err` here
                    // what sould I put instead ? 
                    os.Exit(0)
                }
            }
        }()
    }
    wg.Wait()

    return nil
}

In each goroutine, the function work() is called 10 times. If one call to work() returns an error in any of the running goroutines, I want all the goroutines to stop immediately, and the program to exit. Is it ok to use os.Exit() here ? How should I handle this ?


Edit: this question is different from how to stop a goroutine as here I need to close all goroutines if an error occurs in one

3 Answers

A more clear way to go here is to use errgroup (documentation).

Package errgroup provides synchronization, error propagation, and Context cancelation for groups of goroutines working on subtasks of a common task.

You can check it out in this example (playground):

    var g errgroup.Group
    var urls = []string{
        "http://www.golang.org/",
        "http://www.google.com/",
        "http://www.somestupidname.com/",
    }

    for _, url := range urls {
        // Launch a goroutine to fetch the URL.
        url := url // https://golang.org/doc/faq#closures_and_goroutines
        
       g.Go(func() error {
            // Fetch the URL.
            resp, err := http.Get(url)
            if err == nil {
                resp.Body.Close()
            }
            return err
        })
    }
   
    // Wait for all HTTP fetches to complete.
    if err := g.Wait(); err == nil {
        fmt.Println("Successfully fetched all URLs.")
    
    } else {

        // After all have run, at least one of them has returned an error!
       // But all have to finish their work!
       // If you want to stop others goroutines when one fail, go ahead reading!
        fmt.Println("Unsuccessfully fetched URLs.")
    }

But attention: The first call to return a non-nil error cancels the group phrase in the Go documentation is a little bit misleading.

In fact, errgroup.Group if created with a context (WithContext function), will call the cancel function of the context returned by WithContext when a goroutine in the group will return an error, otherwise nothing will be done (read the source code here!).

So, if you want to close your different goroutines, you must use the context returned my WithContext and manage it by yourself inside them, errgroup will just close that context! Here you can find an example.

To summarize, errgroup can be used in different ways, as shown by the examples.

  1. "just errors", as the above example: Wait wait that all goroutines end, and then returns the first non-nil error if any from them, or return nil.

  2. In parallel: You have to create the group with the WithContext function and use the context to manage the context closing. I created a playground example here with some sleeps! You have to manually close each goroutines, but using the context you can end them when one close the context.

  3. Pipelines (see more in the examples).

Another way to go here is to use errgroup.WithContext. You can check it out in this example.

In short, g.Wait() waits for the first error to happen or for all to finish without errors. When error happens in any of the goroutines (timeout in the provided example), it cancels the execution in other goroutines through ctx.Done() channel.

Related