internal/signals/signals.go (view raw)
1// Package signals provides cross-platform signal handling for graceful shutdown.
2package signals
3
4import (
5 "os"
6 "os/signal"
7 "sync/atomic"
8 "syscall"
9)
10
11// Signals watched for graceful shutdown.
12var (
13 SigInt = os.Signal(syscall.SIGINT)
14 SigTerm = os.Signal(syscall.SIGTERM)
15 SigHup = os.Signal(syscall.SIGHUP)
16)
17
18// SignalHandler coordinates signal watching and exit requests.
19type SignalHandler struct {
20 notify chan os.Signal
21 done chan struct{}
22 exiting atomic.Bool
23}
24
25// New creates a new SignalHandler.
26func New() *SignalHandler {
27 return &SignalHandler{
28 notify: make(chan os.Signal, 1),
29 done: make(chan struct{}),
30 }
31}
32
33// Watch starts the signal listener goroutine and returns immediately.
34// The goroutine exits cleanly when Stop() is called.
35func (sh *SignalHandler) Watch() {
36 signal.Notify(sh.notify, SigInt, SigTerm, SigHup)
37 go func() {
38 select {
39 case <-sh.notify:
40 sh.exiting.Store(true)
41 case <-sh.done:
42 return
43 }
44 }()
45}
46
47// IsExiting returns true if an exit has been requested (by signal).
48func (sh *SignalHandler) IsExiting() bool {
49 return sh.exiting.Load()
50}
51
52// Stop stops signal watching. The Watch() goroutine exits immediately.
53// Safe to call from any goroutine, including signal handlers.
54func (sh *SignalHandler) Stop() {
55 signal.Stop(sh.notify)
56 select {
57 case sh.done <- struct{}{}:
58 default:
59 }
60}