Skip to content

Commit eb929b0

Browse files
authored
Add handler to shutdown daemon routines gracefully (#78)
This is mostly made for http.Server which exposes a Shutdown method taking a context for timeout
1 parent 2a079b7 commit eb929b0

File tree

1 file changed

+86
-0
lines changed

1 file changed

+86
-0
lines changed

graceful/graceful.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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 is a target that 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 target to shutdown gracefully
36+
func (cc *Closer) Register(name string, shut Shutdownable, timeout time.Duration) {
37+
cc.targetsMutex.Lock()
38+
cc.targets = append(cc.targets, target{
39+
name: name,
40+
shut: shut,
41+
timeout: timeout,
42+
})
43+
cc.targetsMutex.Unlock()
44+
}
45+
46+
// DetectShutdown asynchronously waits for a shutdown signal and then shuts down gracefully
47+
// Returns a function to trigger a shutdown from the outside, like cancelling a context
48+
func (cc *Closer) DetectShutdown(log logrus.FieldLogger) func() {
49+
go func() {
50+
signals := make(chan os.Signal, 1)
51+
signal.Notify(signals, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
52+
select {
53+
case sig := <-signals:
54+
log.Infof("Triggering shutdown from signal %s", sig)
55+
case <-cc.done:
56+
log.Infof("Shutting down...")
57+
}
58+
59+
if atomic.SwapInt32(&cc.doneBool, 1) != 1 {
60+
wg := sync.WaitGroup{}
61+
cc.targetsMutex.Lock()
62+
for _, targ := range cc.targets {
63+
wg.Add(1)
64+
go func(targ target, log logrus.FieldLogger) {
65+
defer wg.Done()
66+
67+
ctx, cancel := context.WithTimeout(context.Background(), targ.timeout)
68+
defer cancel()
69+
70+
if err := targ.shut.Shutdown(ctx); err != nil {
71+
log.WithError(err).Error("Graceful shutdown failed")
72+
} else {
73+
log.Info("Shutdown finished")
74+
}
75+
}(targ, log.WithField("target", targ.name))
76+
}
77+
cc.targetsMutex.Unlock()
78+
wg.Wait()
79+
os.Exit(0)
80+
}
81+
}()
82+
83+
return func() {
84+
cc.done <- struct{}{}
85+
}
86+
}

0 commit comments

Comments
 (0)