Handling graceful shutdown.
All checks were successful
Deploy Greenlight API / deploy (push) Successful in 1m21s

This commit is contained in:
Maxime Delporte
2025-11-23 16:59:02 +01:00
parent b04086f9da
commit 6b4056e0f5

View File

@@ -1,6 +1,8 @@
package main package main
import ( import (
"context"
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"net/http" "net/http"
@@ -21,6 +23,9 @@ func (app *application) serve() error {
ErrorLog: slog.NewLogLogger(app.logger.Handler(), slog.LevelError), ErrorLog: slog.NewLogLogger(app.logger.Handler(), slog.LevelError),
} }
// Create a shutdownError channel. We will use this to receive any errors returned by the graceful Shutdown() function
shutdownError := make(chan error)
// Start a background goroutine // Start a background goroutine
go func() { go func() {
// Create a quit channel which carries os.Signal values // Create a quit channel which carries os.Signal values
@@ -33,14 +38,33 @@ func (app *application) serve() error {
s := <-quit s := <-quit
// Log a message to say that the signal has been caught. Notice that we also call the String() method on the signal to get the signal name and include it in the log entry attributes // Log a message to say that the signal has been caught. Notice that we also call the String() method on the signal to get the signal name and include it in the log entry attributes
app.logger.Info("caught signal", "signal", s.String()) app.logger.Info("shutting down server", "signal", s.String())
// Exit the application with a 0 (success) status code. // Create a context with a 30-second timeout
os.Exit(0) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Call Shutdown() on our server, passing in the context we just made. Shutdown() will return nil if the graceful shutdown was successful, or an error (which may happen because of a problem closing the listeners, or because the shutdown didn't complete before the 30-second context deadline is hit). We relay this return value to the shutdownError channel.
shutdownError <- srv.Shutdown(ctx)
}() }()
// Start the HTTP server. // Start the HTTP server.
app.logger.Info("starting server", "addr", srv.Addr, "env", app.config.env) app.logger.Info("starting server", "addr", srv.Addr, "env", app.config.env)
return srv.ListenAndServe() // Calling Shutdown() on our server will cause ListenAndServe() to immediately return a http.ErrServerClosed error. So if we see this error, it is actually a good thing and an indication that the graceful shutdown has started. So we check specifically for this, only returning the error if it is NOT http.ErrServerClosed.
err := srv.ListenAndServe()
if !errors.Is(err, http.ErrServerClosed) {
return err
}
// Otherwise, we wait to receive the return value from Shutdown() on the shutdownError channel. If return value is an error, we know that there was a problem with the graceful shutdown and we return the error
err = <-shutdownError
if err != nil {
return err
}
// At this point, we know that the graceful shutdown completed successfully, and we log a "stopped server" message
app.logger.Info("stopped server", "addr", srv.Addr)
return nil
} }