Gracefully stopping goroutines
Go’s concurrency is great, but how do we end an application gracefully?
July 8, 2015
Recently I developed StrangerBot, a bot built using the Telegram Bot API. It’s pretty cool. It got more than 10,000 users in a week.
This bot was quickly hacked together, so I keep improving it and deploying fixes on a daily basis. I make use of channels that holds the received messages, and redeploying causes a loss of these messages.
My application contains one goroutine, that continuously tries to receive the newest messages sent to the bot. Once received it passes them into a channel and they are handled by several workers.
So I went to figure out how to gracefully stop all running goroutines before quitting the application, in order to prevent losing the messages.
I ended up using a sync.WaitGroup
and a channel that listens to signals. See the example:
package main
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
func worker(jobs <-chan int) {
for job := range jobs {
fmt.Println(job)
time.Sleep(500 * time.Millisecond)
}
}
func main() {
jobs := make(chan int, 1000)
var wg sync.WaitGroup
// Spawn example workers
for i := 0; i < 3; i++ {
wg.Add(1)
go func(jobs <-chan int) {
defer wg.Done()
worker(jobs)
fmt.Println("Worker is done.")
}(jobs)
}
// Create example messages
for i := 0; i < 100; i++ {
jobs <- i
}
waitForSignal()
close(jobs)
fmt.Println("Closed jobs!")
wg.Wait()
}
func waitForSignal() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt)
signal.Notify(sigs, syscall.SIGTERM)
<-sigs
}
In this example the application will block on waitForSignal()
. When a signal is
received the jobs
channel will be closed. To prevent the loss of messages we
use a WaitGroup
, this way we can make sure all goroutines finished
running, otherwise the application will still exit immediately and we will still
lose messages.
Be wary though, once you’re using multiple channels that communicate with each
other the solution might not be as simple as this, and you might require several
WaitGroups
in order to achieve a graceful shutdown without losing any messages.
Hope it was helpful :).