Skip to content

Commit a4682af

Browse files
committed
Refactor usage of graceful from netlify-commons
1 parent 1fcc5aa commit a4682af

File tree

2 files changed

+123
-6
lines changed

2 files changed

+123
-6
lines changed

api/api.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"net/http"
66
"regexp"
7+
"time"
78

89
"github.com/jinzhu/gorm"
910
"github.com/sebest/xff"
@@ -15,7 +16,7 @@ import (
1516
"github.com/go-chi/chi"
1617
"github.com/netlify/gocommerce/conf"
1718
gcontext "github.com/netlify/gocommerce/context"
18-
"github.com/netlify/netlify-commons/graceful"
19+
"github.com/netlify/gocommerce/graceful"
1920
)
2021

2122
const (
@@ -38,13 +39,17 @@ type API struct {
3839
// ListenAndServe starts the REST API.
3940
func (a *API) ListenAndServe(hostAndPort string) {
4041
log := logrus.WithField("component", "api")
41-
server := graceful.NewGracefulServer(a.handler, log)
42-
if err := server.Bind(hostAndPort); err != nil {
43-
log.WithError(err).Fatal("http server bind failed")
42+
server := &http.Server{
43+
Addr: hostAndPort,
44+
Handler: a.handler,
4445
}
4546

46-
if err := server.Listen(); err != nil {
47-
log.WithError(err).Fatal("http server listen failed")
47+
closer, done := graceful.DetectShutdown(log)
48+
defer done()
49+
closer.Register("api", server, time.Second*60)
50+
51+
if err := server.ListenAndServe(); err != nil {
52+
log.WithError(err).Fatal("API server failed")
4853
}
4954
}
5055

graceful/graceful.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package graceful
2+
3+
import (
4+
"context"
5+
"os"
6+
"os/signal"
7+
"sync"
8+
"sync/atomic"
9+
"syscall"
10+
"time"
11+
12+
"github.com/sirupsen/logrus"
13+
)
14+
15+
// Shutdownable can be closed gracefully
16+
type Shutdownable interface {
17+
Shutdown(context.Context) error
18+
}
19+
20+
type target struct {
21+
name string
22+
shut Shutdownable
23+
timeout time.Duration
24+
}
25+
26+
// Closer handles shutdown of servers and connections
27+
type Closer struct {
28+
targets []target
29+
targetsMutex sync.Mutex
30+
31+
done chan struct{}
32+
doneBool int32
33+
}
34+
35+
// Register inserts a subject to shutdown gracefully
36+
func (cc *Closer) Register(name string, shut Shutdownable, timeout time.Duration) {
37+
if atomic.LoadInt32(&cc.doneBool) != 0 {
38+
return
39+
}
40+
41+
cc.targetsMutex.Lock()
42+
cc.targets = append(cc.targets, target{
43+
name: name,
44+
shut: shut,
45+
timeout: timeout,
46+
})
47+
cc.targetsMutex.Unlock()
48+
}
49+
50+
// DetectShutdown waits for a shutdown signal and then shuts down gracefully
51+
func DetectShutdown(log logrus.FieldLogger) (*Closer, func()) {
52+
cc := new(Closer)
53+
54+
go func() {
55+
WaitForShutdown(log, cc.done)
56+
57+
if atomic.SwapInt32(&cc.doneBool, 1) != 1 {
58+
log.Debugf("Initiating shutdown of %d targets", len(cc.targets))
59+
wg := sync.WaitGroup{}
60+
cc.targetsMutex.Lock()
61+
for _, targ := range cc.targets {
62+
wg.Add(1)
63+
go func(targ target, log logrus.FieldLogger) {
64+
defer wg.Done()
65+
slog := log.WithField("target", targ.name)
66+
ctx, cancel := context.WithTimeout(context.Background(), targ.timeout)
67+
defer cancel()
68+
slog.Debug("Triggering shutdown")
69+
if err := targ.shut.Shutdown(ctx); err != nil {
70+
log.WithError(err).Error("Graceful shutdown failed")
71+
}
72+
slog.Debug("Shutdown finished")
73+
}(targ, log.WithField("target", targ.name))
74+
}
75+
cc.targetsMutex.Unlock()
76+
log.Debugln("Waiting for targets to finish shutdown")
77+
wg.Wait()
78+
os.Exit(0)
79+
}
80+
}()
81+
return cc, func() {
82+
cc.done <- struct{}{}
83+
}
84+
}
85+
86+
// WaitForShutdown blocks until the system signals termination or done has a value
87+
func WaitForShutdown(log logrus.FieldLogger, done <-chan struct{}) {
88+
signals := make(chan os.Signal, 1)
89+
signal.Notify(signals, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
90+
select {
91+
case sig := <-signals:
92+
log.Infof("Triggering shutdown from signal %s", sig)
93+
case <-done:
94+
log.Infof("Shutting down...")
95+
}
96+
}
97+
98+
// ShutdownContext returns a context that is cancelled on termination
99+
func ShutdownContext(ctx context.Context, log logrus.FieldLogger) (context.Context, func()) {
100+
done := make(chan struct{})
101+
shut := func() {
102+
close(done)
103+
}
104+
105+
ctx, cancel := context.WithCancel(ctx)
106+
go func() {
107+
defer cancel()
108+
WaitForShutdown(log, done)
109+
}()
110+
111+
return ctx, shut
112+
}

0 commit comments

Comments
 (0)