package main import ( "context" "errors" "fmt" "net/http" "os" "os/signal" "syscall" "time" ) func (app *application) serve() error { srv := &http.Server{ Addr: fmt.Sprintf(":%d", app.config.port), Handler: app.routes(), IdleTimeout: time.Minute, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, } shutdownError := make(chan error) go func() { quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) s := <-quit app.logger.PrintInfo("caught signal", map[string]string{ "signal": s.String(), }) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Call Shutdown() on the server like before, but now we only send on the // shutdownError channel if it returns an error. err := srv.Shutdown(ctx) if err != nil { shutdownError <- err } // Log a message to say that we're waiting for any background goroutines to // complete their tasks. app.logger.PrintInfo("completing background tasks", map[string]string{ "addr": srv.Addr, }) // Call Wait() to block until our WaitGroup counter is zero --- essentially // blocking until the background goroutines have finished. Then we return nil on // the shutdownError channel, to indicate that the shutdown completed without // any issues. app.wg.Wait() shutdownError <- nil }() app.logger.PrintInfo("starting server", map[string]string{ "addr": srv.Addr, "env": app.config.env, }) err := srv.ListenAndServe() if !errors.Is(err, http.ErrServerClosed) { return err } err = <-shutdownError if err != nil { return err } app.logger.PrintInfo("stopped server", map[string]string{ "addr": srv.Addr, }) return nil }