-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgracefulshutdown.go
302 lines (245 loc) · 8.28 KB
/
gracefulshutdown.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/*
Providing shutdown callbacks for graceful app shutdown
Installation
To install run:
go get github.com/Zemanta/gracefulshutdown
Example - posix signals
Graceful shutdown will listen for posix SIGINT and SIGTERM signals.
When they are received it will run all callbacks in separate go routines.
When callbacks return, the application will exit with os.Exit(0)
package main
import (
"fmt"
"time"
"github.com/Zemanta/gracefulshutdown"
"github.com/Zemanta/gracefulshutdown/shutdownmanagers/posixsignal"
)
func main() {
// initialize gracefulshutdown
gs := gracefulshutdown.New()
// add posix shutdown manager
gs.AddShutdownManager(posixsignal.NewPosixSignalManager())
// add your tasks that implement ShutdownCallback
gs.AddShutdownCallback(gracefulshutdown.ShutdownFunc(func(string) error {
fmt.Println("Shutdown callback start")
time.Sleep(time.Second)
fmt.Println("Shutdown callback finished")
return nil
}))
// start shutdown managers
if err := gs.Start(); err != nil {
fmt.Println("Start:", err)
return
}
// do other stuff
time.Sleep(time.Hour)
}
Example - posix signals with error handler
The same as above, except now we set an ErrorHandler that prints the
error returned from ShutdownCallback.
package main
import (
"fmt"
"time"
"errors"
"github.com/Zemanta/gracefulshutdown"
"github.com/Zemanta/gracefulshutdown/shutdownmanagers/posixsignal"
)
func main() {
// initialize gracefulshutdown
gs := gracefulshutdown.New()
// add posix shutdown manager
gs.AddShutdownManager(posixsignal.NewPosixSignalManager())
// set error handler
gs.SetErrorHandler(gracefulshutdown.ErrorFunc(func(err error) {
fmt.Println("Error:", err)
}))
// add your tasks that implement ShutdownCallback
gs.AddShutdownCallback(gracefulshutdown.ShutdownFunc(func(string) error {
fmt.Println("Shutdown callback start")
time.Sleep(time.Second)
fmt.Println("Shutdown callback finished")
return errors.New("my-error")
}))
// start shutdown managers
if err := gs.Start(); err != nil {
fmt.Println("Start:", err)
return
}
// do other stuff
time.Sleep(time.Hour)
}
Example - aws
Graceful shutdown will listen for SQS messages on "example-sqs-queue".
If a termination message has current EC2 instance id,
it will run all callbacks in separate go routines.
While callbacks are running it will call aws api
RecordLifecycleActionHeartbeatInput autoscaler every 15 minutes.
When callbacks return, the application will call aws api CompleteLifecycleAction.
The callback will delay only if shutdown was initiated by awsmanager.
If the message does not have current instance id, it will forward the
message to correct instance via http on port 7999.
package main
import (
"fmt"
"time"
"github.com/Zemanta/gracefulshutdown"
"github.com/Zemanta/gracefulshutdown/shutdownmanagers/awsmanager"
"github.com/Zemanta/gracefulshutdown/shutdownmanagers/posixsignal"
)
func main() {
// initialize gracefulshutdown with ping time
gs := gracefulshutdown.New()
// add posix shutdown manager
gs.AddShutdownManager(posixsignal.NewPosixSignalManager())
// set error handler
gs.SetErrorHandler(gracefulshutdown.ErrorFunc(func(err error) {
fmt.Println("Error:", err)
}))
// add aws shutdown manager
gs.AddShutdownManager(awsmanager.NewAwsManager(&awsmanager.AwsManagerConfig{
SqsQueueName: "example-sqs-queue",
LifecycleHookName: "example-lifecycle-hook",
Port: 7999,
}))
// add your tasks that implement ShutdownCallback
gs.AddShutdownCallback(gracefulshutdown.ShutdownFunc(func(shutdownManager string) error {
fmt.Println("Shutdown callback start")
if shutdownManager == awsmanager.Name {
time.Sleep(time.Hour)
}
fmt.Println("Shutdown callback finished")
return nil
}))
// start shutdown managers
if err := gs.Start(); err != nil {
fmt.Println("Start:", err)
return
}
// do other stuff
time.Sleep(time.Hour * 2)
}
*/
package gracefulshutdown
import (
"sync"
)
// ShutdownCallback is an interface you have to implement for callbacks.
// OnShutdown will be called when shutdown is requested. The parameter
// is the name of the ShutdownManager that requested shutdown.
type ShutdownCallback interface {
OnShutdown(string) error
}
// ShutdownFunc is a helper type, so you can easily provide anonymous functions
// as ShutdownCallbacks.
type ShutdownFunc func(string) error
func (f ShutdownFunc) OnShutdown(shutdownManager string) error {
return f(shutdownManager)
}
// ShutdownManager is an interface implemnted by ShutdownManagers.
// GetName returns the name of ShutdownManager.
// ShutdownManagers start listening for shutdown requests in Start.
// When they call StartShutdown on GSInterface,
// first ShutdownStart() is called, then all ShutdownCallbacks are executed
// and once all ShutdownCallbacks return, ShutdownFinish is called.
type ShutdownManager interface {
GetName() string
Start(gs GSInterface) error
ShutdownStart() error
ShutdownFinish() error
}
// ErrorHandler is an interface you can pass to SetErrorHandler to
// handle asynchronous errors.
type ErrorHandler interface {
OnError(err error)
}
// ErrorFunc is a helper type, so you can easily provide anonymous functions
// as ErrorHandlers.
type ErrorFunc func(err error)
func (f ErrorFunc) OnError(err error) {
f(err)
}
// GSInterface is an interface implemented by GracefulShutdown,
// that gets passed to ShutdownManager to call StartShutdown when shutdown
// is requested.
type GSInterface interface {
StartShutdown(sm ShutdownManager)
ReportError(err error)
AddShutdownCallback(shutdownCallback ShutdownCallback)
}
// GracefulShutdown is main struct that handles ShutdownCallbacks and
// ShutdownManagers. Initialize it with New.
type GracefulShutdown struct {
callbacks []ShutdownCallback
managers []ShutdownManager
errorHandler ErrorHandler
}
// New initializes GracefulShutdown.
func New() *GracefulShutdown {
return &GracefulShutdown{
callbacks: make([]ShutdownCallback, 0, 10),
managers: make([]ShutdownManager, 0, 3),
}
}
// Start calls Start on all added ShutdownManagers. The ShutdownManagers
// start to listen to shutdown requests. Returns an error if any ShutdownManagers
// return an error.
func (gs *GracefulShutdown) Start() error {
for _, manager := range gs.managers {
if err := manager.Start(gs); err != nil {
return err
}
}
return nil
}
// AddShutdownManager adds a ShutdownManager that will listen to shutdown requests.
func (gs *GracefulShutdown) AddShutdownManager(manager ShutdownManager) {
gs.managers = append(gs.managers, manager)
}
// AddShutdownCallback adds a ShutdownCallback that will be called when
// shutdown is requested.
//
// You can provide anything that implements ShutdownCallback interface,
// or you can supply a function like this:
// AddShutdownCallback(gracefulshutdown.ShutdownFunc(func() error {
// // callback code
// return nil
// }))
func (gs *GracefulShutdown) AddShutdownCallback(shutdownCallback ShutdownCallback) {
gs.callbacks = append(gs.callbacks, shutdownCallback)
}
// SetErrorHandler sets an ErrorHandler that will be called when an error
// is encountered in ShutdownCallback or in ShutdownManager.
//
// You can provide anything that implements ErrorHandler interface,
// or you can supply a function like this:
// SetErrorHandler(gracefulshutdown.ErrorFunc(func (err error) {
// // handle error
// }))
func (gs *GracefulShutdown) SetErrorHandler(errorHandler ErrorHandler) {
gs.errorHandler = errorHandler
}
// StartShutdown is called from a ShutdownManager and will initiate shutdown:
// first call ShutdownStart on Shutdownmanager,
// call all ShutdownCallbacks, wait for callbacks to finish and
// call ShutdownFinish on ShutdownManager
func (gs *GracefulShutdown) StartShutdown(sm ShutdownManager) {
gs.ReportError(sm.ShutdownStart())
var wg sync.WaitGroup
for _, shutdownCallback := range gs.callbacks {
wg.Add(1)
go func(shutdownCallback ShutdownCallback) {
defer wg.Done()
gs.ReportError(shutdownCallback.OnShutdown(sm.GetName()))
}(shutdownCallback)
}
wg.Wait()
gs.ReportError(sm.ShutdownFinish())
}
// ReportError is a function that can be used to report errors to
// ErrorHandler. It is used in ShutdownManagers.
func (gs *GracefulShutdown) ReportError(err error) {
if err != nil && gs.errorHandler != nil {
gs.errorHandler.OnError(err)
}
}