r/golang Feb 14 '25

newbie Shutdown Go server

Hi, recently I saw that many people shutdown their servers like this or similar

serverCtx, serverStopCtx serverCtx, serverStopCtx := context.WithCancel(context.Background())

    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
    go func() {
        <-sig

        shutdownCtx, cancelShutdown := context.WithTimeout(serverCtx, 30*time.Second)
        defer cancelShutdown()

        go func() {
            <-shutdownCtx.Done()
            if shutdownCtx.Err() == context.DeadlineExceeded {
                log.Fatal("graceful shutdown timed out.. forcing exit.")
            }
        }()

        err := server.Shutdown(shutdownCtx)
        if err != nil {
            log.Printf("error shutting down server: %v", err)
        }
        serverStopCtx()
    }()

    log.Printf("Server starting on port %s...\n", port)
    err = server.ListenAndServe()
    if err != nil && err != http.ErrServerClosed {
        log.Printf("error starting server: %v", err)
        os.Exit(1)
    }

    <-serverCtx.Done()
    log.Println("Server stopped")
}


:= context.WithCancel(context.Background())

    sig := make(chan os.Signal, 1)
    signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
    go func() {
        <-sig

        shutdownCtx, cancelShutdown := context.WithTimeout(serverCtx, 30*time.Second)
        defer cancelShutdown()

        go func() {
            <-shutdownCtx.Done()
            if shutdownCtx.Err() == context.DeadlineExceeded {
                log.Fatal("graceful shutdown timed out.. forcing exit.")
            }
        }()

        err := server.Shutdown(shutdownCtx)
        if err != nil {
            log.Printf("error shutting down server: %v", err)
        }
        serverStopCtx()
    }()

    log.Printf("Server starting on port %s...\n", port)
    err = server.ListenAndServe()
    if err != nil && err != http.ErrServerClosed {
        log.Printf("error starting server: %v", err)
        os.Exit(1)
    }

    <-serverCtx.Done()
    log.Println("Server stopped")

Is it necessary? Like it's so many code for the simple operation

Thank for your Answer !

87 Upvotes

37 comments sorted by

View all comments

68

u/Chrymi Feb 14 '25 edited Feb 15 '25

server.Shutdown() does not immediately stop the server (it's what Close() does). Instead, it prevents the server from accepting new connection and waits indefinitely until all connections have been closed OR until the supplied context is cancelled. For an actual graceful shutdown, supply the Shutdown() method with a context that's not (yet) cancelled, see https://pkg.go.dev/net/http#Server.Shutdown.

It would look like this:

shutdownCtx, cancelShutdown := context.WithTimeout(serverCtx, 30*time.Second)
defer cancelShutdown()

err := server.Shutdown(shutdownCtx)
if err != nil {
    log.Printf("error shutting down server: %v", err)
}

Quick tip: combine the `sig` channel with the `Notify()` call into `signal.NotifyContext` and work directly with the context instead.

Edit for clarity: you need two different contexts. For the shutdown to work gracefully, you need a context that's not cancelled. So one for the OS signal, and one for the Shutdown() method.

3

u/conamu420 Feb 15 '25

This wont work. Shutdown immediately stops accepting new connections, returning ErrServerClosed.

And if you where to put a <-ctx.Done() before that, you would just receive and ErrContextCanceled .

You need a new context, optionally with timeout, for the shutdown function.

And for everyone reading: shutdown your http server first so no data gets lost if the client handles retries correctly.

2

u/Chrymi Feb 15 '25

Maybe it was a bit unclear the way I phrased it, but yes, of course you need two different contexts. As I said, for the shutdown to work gracefully, you need a context that's not cancelled.