master
go 75 lines 1.73 KB
Raw
1 // Signal handling. Excluded from wasm where os.Signal is unavailable.
2 //go:build !wasm
3
4 package util
5
6 import (
7 "context"
8 "fmt"
9 "io"
10 "os"
11 "os/signal"
12 "sync"
13 "syscall"
14 )
15
16 // IntrHandler helps set up an interrupt handler that can
17 // be cleanly shut down through the io.Closer interface.
18 type IntrHandler struct {
19 closing chan struct{}
20 wg sync.WaitGroup
21 }
22
23 func NewIntrHandler() *IntrHandler {
24 return &IntrHandler{closing: make(chan struct{})}
25 }
26
27 func (ih *IntrHandler) Close() error {
28 close(ih.closing)
29 ih.wg.Wait()
30 return nil
31 }
32
33 // Handle starts handling the given signals, and will call the handler
34 // callback function each time a signal is caught. The function is passed
35 // the number of times the handler has been triggered in total, as
36 // well as the handler itself, so that the handling logic can use the
37 // handler's wait group to ensure clean shutdown when Close() is called.
38 func (ih *IntrHandler) Handle(handler func(count int, ih *IntrHandler), sigs ...os.Signal) {
39 notify := make(chan os.Signal, 1)
40 signal.Notify(notify, sigs...)
41 ih.wg.Go(func() {
42 defer signal.Stop(notify)
43
44 count := 0
45 for {
46 select {
47 case <-ih.closing:
48 return
49 case <-notify:
50 count++
51 handler(count, ih)
52 }
53 }
54 })
55 }
56
57 func SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) {
58 intrh := NewIntrHandler()
59 ctx, cancelFunc := context.WithCancel(ctx)
60
61 handlerFunc := func(count int, ih *IntrHandler) {
62 switch count {
63 case 1:
64 fmt.Println() // Prevent un-terminated ^C character in terminal
65 cancelFunc()
66 default:
67 fmt.Println("Received another interrupt before graceful shutdown, terminating...")
68 os.Exit(-1)
69 }
70 }
71
72 intrh.Handle(handlerFunc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
73
74 return intrh, ctx
75 }