I shall recover - Golang
In this blog post, I intend to demonstrate the usage of Go’s built-in function recover()
used to gracefully handle panics.
The example I prepared is effectively a function that takes another function as an argument and repeatedly executes it based on the time interval passed in the first parameter.
1package main
2
3import "time"
4
5func main() {
6 runOnInterval(time.Millisecond*500, func(ranAt time.Time) {
7 iWillPanic()
8 })
9}
10
11func iWillPanic() {
12 panic("something went wrong")
13}
The implementation of runOnInterval()
is rather simple, it makes use of a for/loop that blocks on every iteration based in the time interval provided.
1func runOnInterval(interval time.Duration, handler func(ranAt time.Time)) {
2 ticker := time.NewTicker(interval)
3
4 for {
5 handler(time.Now())
6 <-ticker.C
7 }
8}
Such a small program panics every time it’s ran, and it outputs the following information on the terminal:
1panic: something went wrong
2
3goroutine 1 [running]:
4main.iWillPanic(...)
A naive assumption…
What if the function passed in runOnInterval()
is not meant to panic? And that perhaps if something unexpected were to happen runOnInterval()
should at least retry it three times before stopping the execution altogether. That’s when the recover()
function enters the conversation, and its basic syntax can be defined as such:
1package main
2
3func iWillPanic() {
4 panic("Yeah, I refuse to run")
5}
6
7func main() {
8 defer func() {
9 if r := recover(); r != nil {
10 log.Println("Oops, something went wrong: ", r)
11 }
12 }()
13 iWillPanic()
14}
recover
must be called within a deferred function. When the enclosing function panics, the defer will activate and a recover call within it will catch the panic. - Go by Example: Recover
Now let’s recover
1package main
2
3import (
4 "time"
5 "log"
6)
7
8func runOnInterval(interval time.Duration, handler func(ranAt time.Time)) {
9 ticker := time.NewTicker(interval)
10 for {
11 func () {
12 defer func() {
13 if r := recover(); r != nil {
14 log.Printf("The handler has panicked: %v", r)
15 }
16 }()
17 handler(time.Now())
18 }()
19 <-ticker.C
20 }
21}
The use of defer
inside a loop is discouraged, but runOnInterval
still needs to check whether the handler
has panicked and to do that I wrapped both the recover
and the handler inside an anonymous function that is going check whether the handler
has panicked or not. Here are the logs shown in the terminal after the change made above:
2023/07/05 08:25:39 The handler has panicked: something went wrong
2023/07/05 08:25:40 The handler has panicked: something went wrong
2023/07/05 08:25:40 The handler has panicked: something went wrong
2023/07/05 08:25:41 The handler has panicked: something went wrong
2023/07/05 08:25:41 The handler has panicked: something went wrong
2023/07/05 08:25:42 The handler has panicked: something went wrong
2023/07/05 08:25:42 The handler has panicked: something went wrong
2023/07/05 08:25:43 The handler has panicked: something went wrong
2023/07/05 08:25:43 The handler has panicked: something went wrong
It works, but the implementation is rather incomplete because runOnInterval
will attempt to recover forever, which is not ideal because there is no guarantee that whatever is making the handler
panic will ever change. As a countermeasure, a counter - no pun intended - comes in quite handy:
1package main
2
3import (
4 "time",
5 "log"
6)
7
8func runOnInterval(interval time.Duration, handler func(ranAt time.Time)) {
9 panicCounter := 0
10 ticker := time.NewTicker(interval)
11
12 for {
13 if panicCounter == 3 {
14 log.Printf("The handler has panicked %d times. Ending execution", panicCounter)
15 break
16 }
17
18 func() {
19 defer func() {
20 if r := recover(); r != nil {
21 panicCounts++
22 log.Printf("The handler has panicked. Panic count: %d", panicCounter)
23 }
24 }()
25 handler(time.Now())
26 }()
27
28 <-ticker.C
29 }
30}
31
32func main() {
33 runOnInterval(time.Millisecond*500, func(ranAt time.Time) {
34 iWillPanic()
35 })
36}
37
38func iWillPanic() {
39 panic("something went wrong")
40}
Here are the logs that the program outputs with the introduction of the counter:
2023/07/05 08:29:33 The handler has panicked. Panic count: 1
2023/07/05 08:29:34 The handler has panicked. Panic count: 2
2023/07/05 08:29:34 The handler has panicked. Panic count: 3
2023/07/05 08:29:35 The handler has panicked 3 times. Ending execution
That’s it for this blog post, and thank you for passing by.