-
Notifications
You must be signed in to change notification settings - Fork 0
/
signal_handler.go
146 lines (117 loc) · 3.28 KB
/
signal_handler.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
package gograce
import (
"context"
"log"
"os"
"sync/atomic"
"syscall"
"os/signal"
)
type ForceFunc func()
var (
defaultSignals = [...]os.Signal{syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP}
)
// SignalHandlerOptions
type SignalHandlerOptions struct {
// Force enables quiting forcefully (by sending one of the Signals twice)
// when graceful shutdown is in progress
Force bool
// Signals overwrites the defaultSignals.
Signals []os.Signal
// ForceFunc is called when Force = true and one of the Signals is sent twice.
// If ForceFunc is nil, defaultForceFunc will be used which is os.Exit(1).
ForceFunc ForceFunc
}
// A SignalHandler listens for signals and handles graceful and forceful shutdown
// When a signal has been sent twice SignalHandler will call forceFunc. Default
// forceFunc is os.Exit(1) so application will terminate.
type SignalHandler struct {
signals []os.Signal
force bool
sigChan chan os.Signal
forceFunc ForceFunc
started atomic.Bool
}
// NewSignalHandler will create a signal handler based on the desired opts given.
// It will then start the signal handler as well.
func NewSignalHandler(ctx context.Context, opts SignalHandlerOptions) (*SignalHandler, context.Context) {
if len(opts.Signals) == 0 {
opts.Signals = defaultSignals[:]
}
if opts.ForceFunc == nil {
opts.ForceFunc = defaultForceFunc
}
sh := &SignalHandler{
signals: opts.Signals,
force: opts.Force,
started: atomic.Bool{},
forceFunc: opts.ForceFunc,
}
ctx = sh.Start(ctx)
return sh, ctx
}
// Start will start the SignalHandler by listening to signals and parent context.
// If at anypoint parent context gets canceled, Start will return. It is safe
// but useless to call Start from multiple go-routines because it will start it
// the first and you have to Close it first to be able to Start it again.
func (s *SignalHandler) Start(ctx context.Context) context.Context {
if s.started.Swap(true) {
return ctx // TODO should this be nil or not?
}
// at any point we need to stop execution when the
// parent context gets canceled so we make a copy
// of it.
parentCtx := ctx
ctx, cancel := context.WithCancel(ctx)
s.sigChan = make(chan os.Signal, 1)
go func() {
signal.Notify(s.sigChan, s.signals...)
defer s.Close()
var (
sig os.Signal
ok bool
)
select {
case sig, ok = <-s.sigChan:
if !ok {
log.Println("signal channel closed quiting...")
return
}
log.Printf("received signal '%s', gracefully quitting...\n", sig)
cancel()
case <-parentCtx.Done():
log.Printf("parent context canceled\n")
return
}
if s.force {
select {
case sig, ok = <-s.sigChan:
if !ok {
log.Println("signal channel closed quiting...")
return
}
log.Printf("received signal '%s', forcefully quitting...\n", sig)
cancel()
case <-parentCtx.Done():
log.Printf("parent context canceled, while waiting for second signal\n")
return
}
s.Close()
s.forceFunc()
return
}
s.Close()
}()
return ctx
}
// Close closes sigChan. Calls to close only work when SignalHandler has been started
// and other wise it has no effect. It is also safe to call it from multiple go-routines.
func (sh *SignalHandler) Close() {
if sh.started.Swap(false) {
return
}
close(sh.sigChan)
}
func defaultForceFunc() {
os.Exit(1)
}