github.com/decred/dcrlnd@v0.7.6/signal/signal.go (about) 1 // Copyright (c) 2013-2017 The btcsuite developers 2 // Copyright (c) 2015-2016 The Decred developers 3 // Heavily inspired by https://github.com/decred/dcrd/blob/master/signal.go 4 // Copyright (C) 2015-2017 The Lightning Network Developers 5 6 package signal 7 8 import ( 9 "errors" 10 "fmt" 11 "os" 12 "os/signal" 13 "sync/atomic" 14 "syscall" 15 16 "github.com/coreos/go-systemd/daemon" 17 ) 18 19 var ( 20 // started indicates whether we have started our main interrupt handler. 21 // This field should be used atomically. 22 started int32 23 ) 24 25 // systemdNotifyReady notifies systemd about LND being ready, logs the result of 26 // the operation or possible error. Besides logging, systemd being unavailable 27 // is ignored. 28 func systemdNotifyReady() error { 29 notified, err := daemon.SdNotify(false, daemon.SdNotifyReady) 30 if err != nil { 31 err := fmt.Errorf("failed to notify systemd %v (if you aren't "+ 32 "running systemd clear the environment variable "+ 33 "NOTIFY_SOCKET)", err) 34 log.Error(err) 35 36 // The SdNotify doc says it's common to ignore the 37 // error. We don't want to ignore it because if someone 38 // set up systemd to wait for initialization other 39 // processes would get stuck. 40 return err 41 } 42 if notified { 43 log.Info("Systemd was notified about our readiness") 44 } else { 45 log.Info("We're not running within systemd") 46 } 47 return nil 48 } 49 50 // systemdNotifyStop notifies systemd that LND is stopping and logs error if 51 // the notification failed. It also logs if the notification was actually sent. 52 // Systemd being unavailable is intentionally ignored. 53 func systemdNotifyStop() { 54 notified, err := daemon.SdNotify(false, daemon.SdNotifyStopping) 55 56 // Just log - we're stopping anyway. 57 if err != nil { 58 log.Errorf("Failed to notify systemd: %v", err) 59 } 60 if notified { 61 log.Infof("Systemd was notified about stopping") 62 } 63 } 64 65 // Notifier handles notifications about status of LND. 66 type Notifier struct { 67 // notifiedReady remembers whether Ready was sent to avoid sending it 68 // multiple times. 69 notifiedReady bool 70 } 71 72 // NotifyReady notifies other applications that RPC is ready. 73 func (notifier *Notifier) NotifyReady(walletUnlocked bool) error { 74 if !notifier.notifiedReady { 75 err := systemdNotifyReady() 76 if err != nil { 77 return err 78 } 79 notifier.notifiedReady = true 80 } 81 if walletUnlocked { 82 _, _ = daemon.SdNotify(false, "STATUS=Wallet unlocked") 83 } else { 84 _, _ = daemon.SdNotify(false, "STATUS=Wallet locked") 85 } 86 87 return nil 88 } 89 90 // notifyStop notifies other applications that LND is stopping. 91 func (notifier *Notifier) notifyStop() { 92 systemdNotifyStop() 93 } 94 95 // Interceptor contains channels and methods regarding application shutdown 96 // and interrupt signals. 97 type Interceptor struct { 98 // interruptChannel is used to receive SIGINT (Ctrl+C) signals. 99 interruptChannel chan os.Signal 100 101 // shutdownChannel is closed once the main interrupt handler exits. 102 shutdownChannel chan struct{} 103 104 // shutdownRequestChannel is used to request the daemon to shutdown 105 // gracefully, similar to when receiving SIGINT. 106 shutdownRequestChannel chan struct{} 107 108 // quit is closed when instructing the main interrupt handler to exit. 109 // Note that to avoid losing notifications, only shutdown func may 110 // close this channel. 111 quit chan struct{} 112 113 // Notifier handles sending shutdown notifications. 114 Notifier Notifier 115 } 116 117 // Intercept starts the interception of interrupt signals and returns an `Interceptor` instance. 118 // Note that any previous active interceptor must be stopped before a new one can be created. 119 func Intercept() (Interceptor, error) { 120 if !atomic.CompareAndSwapInt32(&started, 0, 1) { 121 return Interceptor{}, errors.New("intercept already started") 122 } 123 124 channels := Interceptor{ 125 interruptChannel: make(chan os.Signal, 1), 126 shutdownChannel: make(chan struct{}), 127 shutdownRequestChannel: make(chan struct{}), 128 quit: make(chan struct{}), 129 } 130 131 signalsToCatch := []os.Signal{ 132 os.Interrupt, 133 os.Kill, 134 syscall.SIGTERM, 135 syscall.SIGQUIT, 136 } 137 signal.Notify(channels.interruptChannel, signalsToCatch...) 138 go channels.mainInterruptHandler() 139 140 return channels, nil 141 } 142 143 // InterceptNoSignal returns an interceptor that does not listen to process 144 // signals and relies on calls to ShutdownRequest to terminate. 145 func InterceptNoSignal() Interceptor { 146 channels := Interceptor{ 147 interruptChannel: make(chan os.Signal, 1), 148 shutdownChannel: make(chan struct{}), 149 shutdownRequestChannel: make(chan struct{}), 150 quit: make(chan struct{}), 151 } 152 153 go func() { 154 <-channels.shutdownRequestChannel 155 log.Infof("Received shutdown request.") 156 close(channels.quit) 157 close(channels.shutdownChannel) 158 }() 159 160 return channels 161 } 162 163 // mainInterruptHandler listens for SIGINT (Ctrl+C) signals on the 164 // interruptChannel and shutdown requests on the shutdownRequestChannel, and 165 // invokes the registered interruptCallbacks accordingly. It also listens for 166 // callback registration. 167 // It must be run as a goroutine. 168 func (c *Interceptor) mainInterruptHandler() { 169 defer atomic.StoreInt32(&started, 0) 170 // isShutdown is a flag which is used to indicate whether or not 171 // the shutdown signal has already been received and hence any future 172 // attempts to add a new interrupt handler should invoke them 173 // immediately. 174 var isShutdown bool 175 176 // shutdown invokes the registered interrupt handlers, then signals the 177 // shutdownChannel. 178 shutdown := func() { 179 // Ignore more than one shutdown signal. 180 if isShutdown { 181 log.Infof("Already shutting down...") 182 return 183 } 184 isShutdown = true 185 log.Infof("Shutting down...") 186 c.Notifier.notifyStop() 187 188 // Signal the main interrupt handler to exit, and stop accept 189 // post-facto requests. 190 close(c.quit) 191 } 192 193 for { 194 select { 195 case signal := <-c.interruptChannel: 196 log.Infof("Received %v", signal) 197 shutdown() 198 199 case <-c.shutdownRequestChannel: 200 log.Infof("Received shutdown request.") 201 shutdown() 202 203 case <-c.quit: 204 log.Infof("Gracefully shutting down.") 205 close(c.shutdownChannel) 206 signal.Stop(c.interruptChannel) 207 return 208 } 209 } 210 } 211 212 // Listening returns true if the main interrupt handler has been started, and 213 // has not been killed. 214 func (c *Interceptor) Listening() bool { 215 // If our started field is not set, we are not yet listening for 216 // interrupts. 217 if atomic.LoadInt32(&started) != 1 { 218 return false 219 } 220 221 // If we have started our main goroutine, we check whether we have 222 // stopped it yet. 223 return c.Alive() 224 } 225 226 // Alive returns true if the main interrupt handler has not been killed. 227 func (c *Interceptor) Alive() bool { 228 select { 229 case <-c.quit: 230 return false 231 default: 232 return true 233 } 234 } 235 236 // RequestShutdown initiates a graceful shutdown from the application. 237 func (c *Interceptor) RequestShutdown() { 238 select { 239 case c.shutdownRequestChannel <- struct{}{}: 240 case <-c.quit: 241 } 242 } 243 244 // ShutdownChannel returns the channel that will be closed once the main 245 // interrupt handler has exited. 246 func (c *Interceptor) ShutdownChannel() <-chan struct{} { 247 return c.shutdownChannel 248 }