Error handling in goroutines with errgroup
Published: 2021-09-21 17:14:52

---
title: Error handling in goroutines with errgroup
published: true
date: 2021-01-11 16:53:52 UTC
tags: 
canonical_url: https://devmarkpro.com/error-handling-goroutines-errgroup
---

[Golang](https://golang.org/) made life easier with **goroutine** , however, sometimes it's difficult to handle errors that happened inside a goroutine effectively. For example, imagine you have an array of some kind of actions and wanted to run a specific function on each one of them. On the other hand, in case of an error, you want to propagate that error to the higher function.

Let's explain it in the code, Imagine that we have a set of `runners` and each runner has a `Handle` function.

```
type HandlerFunc func(input string) error
type Runner struct {
    Name string
    Handle HandlerFunc
}

```

I also like to define another type named `Runners`. It's just a simple wrapper around arrays of runners

```
type Runners []Runner

```

hence I can define a function that runs through all runners like this:

```
func (r Runners) Execute() error {
    for _, runner := range r {
        if err := runner.Handle(runner.Name); err != nil {
            return err
        }
    }
    return nil
}

```

and finally, define some runners and execute them:

```
func main() {
    runners := Runners{
        Runner{
            Name: "1",
            Handle: func(input string) error {
                fmt.Printf("runner %s is running\n", input)
                return nil
            },
        },
        Runner{
            Name: "2",
            Handle: func(input string) error {
                return fmt.Errorf("something bad happened in runner [%s]", input)
            },
        },
        Runner{
            Name: "3",
            Handle: func(input string) error {
                fmt.Printf("runner %s is running\n", input)
                return nil
            },
        },
    }

    err := runners.Execute()
    if err != nil {
        fmt.Printf("execution failed: %v", err)
    }  
}

```

By running this piece of code I get this output: `runner 1 is running`. The problem is we didn't run the third runner. So in this case we print errors and continue running the Execution but we cannot propagate the error to the higher function!

```
func (r Runners) Execute() error {
    for _, runner := range r {
        if err := runner.Handle(runner.Name); err != nil {
      fmt.Printf("error happened in runner [%s]: %v", runner.Name, err)
        }
    }
    return nil
}

```

Now, What if we want to run each runner in a different **goroutine** with the exact same scenario? Let's change a code a little bit to see if it works.

First thing first we add a **go** behind the function call. So our `Execute` function should be something like it:

```
func (r Runners) Execute() error {
    for _, runner := range r {
        go func(runner Runner) error {
            if err := runner.Handle(runner.Name); err != nil {
                return err
            }
            return nil
        }(runner)
    }
    return nil
}

```

However, As you probably know if we run the program again, there will be nothing in output, because we never said the application to wait until the **goroutines** finish their work. for sake of simplicity, let's just add a `Sleep` to the `main` function:

```
err := runners.Execute()
time.Sleep(3 * time.Second)
if err != nil {
    fmt.Printf("execution failed: %v", err)
}

```

Now, let's run it again. this time the output should be something like this:

```
runner 1 is running
runner 3 is running

```

OK, it's not OK! The execution worked well because we could execute runners 1 and 3, however, we still didn't do anything about the error.

### Welcome to errgroup

Now, it's time to solve the problem with [errgroup](https://godoc.org/golang.org/x/sync/errgroup). It's **REALLY** simple and easy. It works like [waitgroups](https://gobyexample.com/waitgroups) under the sync package. Honestly, it's using wait groups behind the scene but since the mentioned scenario is quite common, **errgroup** make life easier for us!

If you're familiar with wait group, You should know what `wg.Done()` and `wg.Wait()` mean. `errgroup` offeres same thing. Let's make things clear in code. First, we declare an `g` variable

```
g := new(errgroup.Group)

```

and run our execute function inside a **goroutine** with `Go` func. so our `Execute` function turns to this:

```
func (r Runners) Execute() {
    for _, runner := range r {
        rx := runner
        g.Go(func() error {
            return rx.Handle(rx.Name)
        })
    }
}

```

the `Go` function gives and `func` which returns an error, if this error is not `nil` you will have that in the returns of the `Wait` function.

Here's the `Wait` func:

```
runners.Execute()
err := g.Wait()
if err != nil {
    fmt.Printf("execution failed: %v", err)
}

```

As you see, we don't need `time.Sleep()` anymore, because the Wait func, waits until the last **goroutine** gets the result and returns the **first** error that happened. If all functions run without error, it returns `nil`

the output is something like it:

```
runner 3 is running
runner 1 is running
execution failed: something bad happened in runner [2]

```

and here's the complete code:

```
package main

import (
    "fmt"

    "golang.org/x/sync/errgroup"
)

var g errgroup.Group

type HandlerFunc func(input string) error

type Runner struct {
    Name string
    Handle HandlerFunc
}

type Runners []Runner

func (r Runners) Execute() {

    for _, runner := range r {
        rx := runner
        g.Go(func() error {
            return rx.Handle(rx.Name)
        })
    }
}

func main() {
    runners := Runners{
        Runner{
            Name: "1",
            Handle: func(input string) error {
                fmt.Printf("runner %s is running\n", input)
                return nil
            },
        },
        Runner{
            Name: "2",
            Handle: func(input string) error {
                return fmt.Errorf("something bad happened in runner [%s]", input)
            },
        },
        Runner{
            Name: "3",
            Handle: func(input string) error {
                fmt.Printf("runner %s is running\n", input)
                return nil
            },
        },
    }
    runners.Execute()
    err := g.Wait()
    if err != nil {
        fmt.Printf("execution failed: %v", err)
    }
}

```