Skip to content

Commit 5811440

Browse files
authored
feat: add graceful shutdown (#285)
With this commit, the backend will stop accepting new connections on SIGINT or SIGTERM, but allow existing requests to finish. This graceful shutdown has a timeout of 25 seconds. Additionally, this removes the double timestamp from the production log format.
1 parent b86f2f1 commit 5811440

File tree

2 files changed

+40
-5
lines changed

2 files changed

+40
-5
lines changed

internal/router/router.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,6 @@ func Router() (*gin.Engine, error) {
121121
controllers.RegisterEnvelopeRoutes(v1.Group("/envelopes"))
122122
controllers.RegisterAllocationRoutes(v1.Group("/allocations"))
123123

124-
log.Info().Msg("backend startup complete")
125-
126124
return r, nil
127125
}
128126

main.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package main
22

33
import (
4+
"context"
45
"io"
6+
"net/http"
57
"os"
8+
"os/signal"
69
"path/filepath"
10+
"syscall"
11+
"time"
712

813
"github.com/envelope-zero/backend/internal/database"
914
"github.com/envelope-zero/backend/internal/router"
@@ -37,7 +42,7 @@ func main() {
3742
if gin.IsDebugging() {
3843
zerolog.SetGlobalLevel(zerolog.DebugLevel)
3944
}
40-
log.Logger = log.Output(output).With().Timestamp().Logger()
45+
log.Logger = log.Output(output).With().Logger()
4146

4247
// Create data directory
4348
dataDir := filepath.Join(".", "data")
@@ -70,7 +75,39 @@ func main() {
7075
log.Fatal().Msg(err.Error())
7176
}
7277

73-
if err := r.Run(); err != nil {
74-
log.Fatal().Msg(err.Error())
78+
// Set the port to the env variable, default to 8080
79+
var port string
80+
if port = os.Getenv("PORT"); port == "" {
81+
port = ":8080"
82+
}
83+
84+
// The following code is taken from https://github.com/gin-gonic/examples/blob/91fb0d925b3935d2c6e4d9336d78cf828925789d/graceful-shutdown/graceful-shutdown/notify-without-context/server.go
85+
// and has been modified to not wait for the
86+
srv := &http.Server{
87+
Addr: port,
88+
Handler: r,
89+
}
90+
91+
// Wait for interrupt signal to gracefully shutdown the server with a timeout of 5 seconds.
92+
quit := make(chan os.Signal, 1)
93+
signal.Notify(quit, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
94+
95+
go func() {
96+
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
97+
log.Fatal().Str("event", "Error during startup").Err(err).Msg("Router")
98+
}
99+
}()
100+
log.Info().Str("event", "Startup complete").Msg("Router")
101+
102+
<-quit
103+
log.Info().Str("event", "Received SIGINT or SIGTERM, stopping gracefully with 25 seconds timeout").Msg("Router")
104+
105+
// Create a context with a 25 second timeout for the server to shut down in
106+
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
107+
defer cancel()
108+
109+
if err := srv.Shutdown(ctx); err != nil {
110+
log.Fatal().Str("event", "Graceful shutdown failed, terminating").Err(err).Msg("Router")
75111
}
112+
log.Info().Str("event", "Backend stopped").Msg("Router")
76113
}

0 commit comments

Comments
 (0)