Generally when I see http servers written by new Go programmers, I see net/http used in a manner that does not handle shutdowns gracefully.

It seems most copy verbatim the Go documentation example into something like this:

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Ok")
    })

    log.Fatal(http.ListenAndServe(":8080", nil))
}

While this works, it glosses over an important aspect on how this applications exits: the program running this one.

What happens if a outside program (such as docker, systemd, sysvinit, etc) wants to shutdown the application as a part of some routine like container node migration, or the host is restarting?

In this case, a problem arises. For example in docker a SIGTERM is sent to the process which as of go1.21.1 defaults to stopping the program immediately.

This means the following program would never print “Shutdown complete”.

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Ok")
    })

    if err := http.ListenAndServe(":8080", nil); err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println("Shutdown complete")
}

This also means that any request that is currently being processed gets immediately stopped without cleanup or finishing the response to the user. Likely that user just sees an io.EOF with no explaination other than the connection has closed.