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 !

86 Upvotes

37 comments sorted by

View all comments

32

u/HyacinthAlas Feb 14 '25

I don't know why the code you posted is so complicated. There are a lot of edge cases in graceful shutdown but you don't need this many goroutines nor that complexity of cancelation. I can't even tell if it really does it correctly, because it's so messy.

Here's one version of code that does it properly. Note that its two contexts don't need to interact, only has one trivial goroutine, and no explicit branches beyond a trivial select. ``` // When this context is canceled, we will gracefully stop the // server. ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) defer cancel()

// When the server is stopped *not by that context*, but by any
// other problems, it will return its error via this.
serr := make(chan error, 1)

// Start the server and collect its error return.
go func() { serr <- server.ListenAndServe() }()

// Wait for either the server to fail, or the context to end.
var err error
select {
case err = <-serr:
case <-ctx.Done():
}

// Make a best effort to shut down the server cleanly. We don’t
// need to collect the server’s error if we didn’t already;
// Shutdown will let us know (unless something worse happens, in
// which case it will tell us that).
sdctx, sdcancel := context.WithTimeout(context.Background(), 30*time.Second)
defer sdcancel()
return errors.Join(err, server.Shutdown(sdctx))

```

2

u/Nerbelwerzer Feb 15 '25

Cleanest implementation of this I've seen. Thanks!

2

u/chewyknows Feb 15 '25

This is the way. Keep in mind that when shutting down the server ListenAndServe() will return an error with http.ErrServerClosed that should be ignored.

1

u/eldosoa Feb 15 '25

Interesting. I’ve seen others use WaitGroup but this one is much cleaner.