-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgrace.go
156 lines (129 loc) · 3.21 KB
/
grace.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package elephantine
import (
"context"
"log/slog"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
// GracefulShutdown is a helper that can be used to listen for SIGINT and
// SIGTERM to gracefully shut down your application.
//
// SIGTERM will trigger a stop, followed by quit after the specified
// timeout. SIGINT will trigger a immediate quit.
type GracefulShutdown struct {
logger *slog.Logger
m sync.Mutex
signals chan os.Signal
stop chan struct{}
quit chan struct{}
}
// NewGracefulShutdown creates a new GracefulShutdown that will wait for
// `timeout` between "stop" and "quit".
func NewGracefulShutdown(logger *slog.Logger, timeout time.Duration) *GracefulShutdown {
return newGracefulShutdown(logger, timeout, true)
}
// NewManualGracefulShutdown creates a GracefulShutdown instance that doesn't
// listen to OS signals.
func NewManualGracefulShutdown(logger *slog.Logger, timeout time.Duration) *GracefulShutdown {
return newGracefulShutdown(logger, timeout, false)
}
func newGracefulShutdown(
logger *slog.Logger, timeout time.Duration,
listenToSignals bool,
) *GracefulShutdown {
gs := GracefulShutdown{
logger: logger,
signals: make(chan os.Signal, 1),
stop: make(chan struct{}),
quit: make(chan struct{}),
}
if listenToSignals {
signal.Notify(gs.signals, syscall.SIGINT, syscall.SIGTERM)
go func() {
for {
if !gs.poll() {
break
}
}
// Stop subscription.
signal.Stop(gs.signals)
}()
}
go func() {
<-gs.stop
select {
case <-gs.quit:
return
default:
logger.Warn("asked to stop, waiting for cleanup",
LogKeyDelay, timeout)
}
time.Sleep(timeout)
logger.Warn("shutting down")
gs.safeClose(gs.quit)
}()
return &gs
}
func (gs *GracefulShutdown) poll() bool {
select {
case sig := <-gs.signals:
gs.handleSignal(sig)
return true
case <-gs.quit:
return false
}
}
func (gs *GracefulShutdown) safeClose(ch chan struct{}) {
gs.m.Lock()
defer gs.m.Unlock()
select {
case <-ch:
default:
close(ch)
}
}
func (gs *GracefulShutdown) handleSignal(sig os.Signal) {
switch sig.String() {
case syscall.SIGINT.String():
gs.logger.Warn("shutting down")
gs.safeClose(gs.quit)
gs.safeClose(gs.stop)
case syscall.SIGTERM.String():
gs.safeClose(gs.stop)
}
}
// Stop triggers a stop, which will trigger quit after the configured timeout.
func (gs *GracefulShutdown) Stop() {
gs.safeClose(gs.stop)
}
// ShouldStop returns a channel that will be closed when stop is triggered.
func (gs *GracefulShutdown) ShouldStop() <-chan struct{} {
return gs.stop
}
// ShouldQuit returns a channel that will be closed when quit is triggered.
func (gs *GracefulShutdown) ShouldQuit() <-chan struct{} {
return gs.quit
}
// CancelOnStop returns a child context that will be cancelled when stop is
// triggered.
func (gs *GracefulShutdown) CancelOnStop(ctx context.Context) context.Context {
cCtx, cancel := context.WithCancel(ctx)
go func() {
<-gs.stop
cancel()
}()
return cCtx
}
// CancelOnQuit returns a child context that will be cancelled when quit is
// triggered.
func (gs *GracefulShutdown) CancelOnQuit(ctx context.Context) context.Context {
cCtx, cancel := context.WithCancel(ctx)
go func() {
<-gs.quit
cancel()
}()
return cCtx
}