Retrying in Go

#quicktip

Mat Ryer
3 min readMay 1, 2015

The mini-package github.com/matryer/try provides a very simple way of retrying code that might depend on unreliable external factors, such as HTTP requests or even reading from network attached storage.

You pass the`try.Do` function a callback that gets called many times until successful. It takes the `attempt` counter (starting at 1) as the only argument, and asks that you return two arguments; a bool and an error which it uses to determine whether the operation was successful or not, and whether to keep trying or not.

var value string
err := try.Do(func(attempt int) (bool, error) {
var err error
value, err = SomeFunction()
return attempt < 5, err // try 5 times
})
if err != nil {
log.Fatalln("error:", err)
}
  • Declare the value variable outside of the `try.Do` call
  • `try.Do` will block until either `nil` is returned as the error (at which point it will return `nil`), or until `false` is returned as first argument (at which point it will give up, and return the error)

Returning an error from the callback function will cause `try` to call it again, and as you might have guessed, returning `nil` indicates success. Returning `attempt < 5` as the first argument tells try to call `SomeFunction` a maximum of five times.

`try.Do` itself returns the last error once it has given up, or nil if it was successful.

Delay between retries

To introduce a delay between retries, just make a `time.Sleep` call before you return from the function (if you are returning an error):

var value string
err := try.Do(func(attempt int) (bool, error) {
var err error
value, err = SomeFunction()
if err != nil {
time.Sleep(1 * time.Minute) // wait a minute
}
return attempt < 5, err
})
if err != nil {
log.Fatalln("error:", err)
}

Handling panics

Panics are another situation which we might want to retry, and we can do so simply by deferring a func that called recover() as usual:

var value string
err := try.Do(func(attempt int) (retry bool, err error) {
retry = attempt < 5 // try 5 times
defer func() {
if r := recover(); r != nil {
err = errors.New(fmt.Sprintf("panic: %v", r))
}
}()
value, err = SomeFunction()
return
})
if err != nil {
log.Fatalln("error:", err)
}
  • Use named return parameters
  • Set retry first
  • Defer the recovery code, and set err manually in the case of a panic
  • Use empty return statement at the end

Panics usually indicate programmer error, so in most cases it wouldn’t make sense to retry the code — it’s likely to fail each time. It depends on how panics are used.

Mini-framework is right

Check out the entire source code for the try package (minus tests), it’s more of a pattern than a package — so feel free to just copy the file into your project to reduce dependencies.

Share something?

If you have a favourite package, tip or trick you want featured, tweet me @matryer.

Go Programming Blueprints #shamelessplug

Learn more about the practicalities of Go with my book Go Programming Blueprints.

--

--

Mat Ryer

Founder at MachineBox.io — Gopher, developer, speaker, author — BitBar app https://getbitbar.com — Author of Go Programming Blueprints