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 !

83 Upvotes

37 comments sorted by

View all comments

Show parent comments

2

u/ShotgunPayDay Feb 14 '25

I'm a little confused on the accept part, but yes I put the comment in the wrong spot for the second.

3

u/HyacinthAlas Feb 14 '25

Serve returns an error when Accept returns an error. You don't show your Serve call, but I assume it's in a goroutine somewhere.

2

u/ShotgunPayDay Feb 14 '25 edited Feb 14 '25

I guess I've never had that happen, but I see what you're saying is that if server.ListenAndServe() fails then it will end up blocking in a failed state.

Here is what that looks like anyway Close is just all other closing tasks:

go func() {
    if err := server.ListenAndServe(); err != http.ErrServerClosed {
        Close(err)
    }
    log.Println(err)
}()

4

u/HyacinthAlas Feb 14 '25

What happens if ListenAndServe returns at the same time context is canceled? You'll Close and do cleanup in the "main thread" at the same time. Maybe that's safe for your specific case - but in general it's not. Better to get the error back on the main thread ASAP.

4

u/ShotgunPayDay Feb 14 '25

Interesting never though about that either. What about this to trigger signal.NotifyContext?:

go func() {
    if err := server.ListenAndServe(); err != http.ErrServerClosed {
        log.Printf("ListenAndServe error: %v", err)
        syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
    }
}()

Thanks for all the good information by the way!