For quite some time, I have been using the following code in my http server application to initiate a graceful shutdown. Stripped off unnecessary codes that aren’t contributing right now.
shutdownChan := make(chan error)
go func() {
quitChan := make(chan os.Signal, 1)
signal.Notify(quitChan, syscall.SIGINT, syscall.SIGTERM)
<-quitChan
ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
defer cancel()
shutdownChan <- srv.Shutdown(ctx)
}()
err := srv.ListenAndServe()
err = <-shutdownChan
To make sure I know what I am doing here, I will rubber duck a bit.
First, shutdownChan
is used to receive errors that might occur during the shutdown process inside the goroutine.
Inside the go func()
function, first we need a way to hold the execution of the goroutine so that it doesn’t exit. We declare a channel for receiving signal, and the <-quitChan
line keeps waiting for the signal and blocks the execution.
When a signal is passed, eg. Ctrl+C is pressed, execution moves to the next line. We create a context which will get a timeout after 10 seconds and call the cancel()
function, aka stop all works and release the resources. Then we call the srv.Shutdown(ctx)
function, return any error to the main goroutine through the previously created shutdownChan
channel.
I hope all clear so far (kindly point out if I have any misunderstanding about any concept). Now I want to do two things.
- Use
signal.NotifyContext
in place ofsignal.Notify
. - Use
errgroup
to return errors from the goroutine instead of using a channel.
What I figured so far about the new code is:
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
defer cancel()
return srv.Shutdown(shutdownCtx)
})
err := g.Wait()
I won’t explain the details what I am doing here since it’s intended to achieve the same thing as before in same concept, but I have this itchy feeling if this would really work. Part of it probably because I am still having doubt about my understanding of how context, channel, errgroup are working under the hood. I have a few questions now.
- Is there any fault in the new code?
- Compared to previous approach and this, which one is overall better and should be recommended over another?
- Can I update the new code to make it better? Or any better graceful shutdown approach?