DEV Community

Julian-Chu
Julian-Chu

Posted on

[Go] Shutdown services in Go - single service

#go

Shut down gracefully is always an important topic. Many things should be considered like how to release resource, how to close active connection properly and so on.

let's step by step go from a basic go http service shutdown to shutdown multiple go services gracefully

Start a http server

func main() {
    srv := &http.Server{
        Addr: ":8080",
    }
    srv.ListenAndServe()
}

The code just starts a http server, but problem is that when an error happens, we get no error message, it's terrible for debug and troubleshooting.

if we look at the ListenAndServe(), we could find the method returns server error.

func (srv *Server) ListenAndServe() error

so we can do something to add log for server error.

Print err for http server

func main() {
    srv := &http.Server{
        Addr: ":8080",
    }
    if err := srv.ListenAndServe(); err != nil {
        log.Fatalf("server err: %s", err)
    }
}

Looks better, now we have error message for debug and troubleshooting.

Signal from OS

Life is not easy, generally we want go application also handle signal from system, like ctrl-c or kill process command
go signal example

And after we receive signal from system, what can we do to shut down the http server?
Go to http package, there's a method call Shutdown: link

// Shutdown gracefully shuts down the server without interrupting any
// active connections. Shutdown works by first closing all open
// listeners, then closing all idle connections, and then waiting
// indefinitely for connections to return to idle and then shut down.
// If the provided context expires before the shutdown is complete,
// Shutdown returns the context's error, otherwise it returns any
// error returned from closing the Server's underlying Listener(s).
//
// When Shutdown is called, Serve, ListenAndServe, and
// ListenAndServeTLS immediately return ErrServerClosed. Make sure the
// program doesn't exit and waits instead for Shutdown to return.
//
// Shutdown does not attempt to close nor wait for hijacked
// connections such as WebSockets. The caller of Shutdown should
// separately notify such long-lived connections of shutdown and wait
// for them to close, if desired. See RegisterOnShutdown for a way to
// register shutdown notification functions.
//
// Once Shutdown has been called on a server, it may not be reused;
// future calls to methods such as Serve will return ErrServerClosed.
func (srv *Server) Shutdown(ctx context.Context) error {

points to pay attention to:

  • this method can close http server gracefully
  • context can be used to control the shutdown timeout
  • after calling this method, ListenAndServe will return ErrServerClosed, but it's not kind of error we could like to catch from server

here's implementation combined with os.signal and shutdown method in http package

func main() {
    shutdown := make(chan os.Signal, 1)
    signal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM)
    srv := &http.Server{
        Addr: ":8080",
    }
    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Server Err: %s", err)
        }
    }()

    <-shutdown

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("Server Shutdown Failed:%+v", err)
    }
    log.Print("Shutdown properly")
}

hints:

  • any os.signal you want to handle can be added in Singal.Notify(channel, signals to handle.....) like variadic args, if no assigned value, it will handle all signals.
  • http server should run on its goroutine, and not catch the http.ErrServerClosed

Top comments (0)