• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 12313002221

13 Dec 2024 09:25AM UTC coverage: 57.486% (+8.6%) from 48.92%
12313002221

push

github

web-flow
Merge pull request #9343 from ellemouton/contextGuard

fn: expand the ContextGuard and add tests

101902 of 177264 relevant lines covered (57.49%)

24909.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/signal/signal.go
1
// Copyright (c) 2013-2017 The btcsuite developers
2
// Copyright (c) 2015-2016 The Decred developers
3
// Heavily inspired by https://github.com/btcsuite/btcd/blob/master/signal.go
4
// Copyright (C) 2015-2022 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 or the service " +
×
46
                        "type is not 'notify'")
×
47
        }
×
48
        return nil
×
49
}
50

51
// systemdNotifyStop notifies systemd that LND is stopping and logs error if
52
// the notification failed. It also logs if the notification was actually sent.
53
// Systemd being unavailable is intentionally ignored.
54
func systemdNotifyStop() {
×
55
        notified, err := daemon.SdNotify(false, daemon.SdNotifyStopping)
×
56

×
57
        // Just log - we're stopping anyway.
×
58
        if err != nil {
×
59
                log.Errorf("Failed to notify systemd: %v", err)
×
60
        }
×
61
        if notified {
×
62
                log.Infof("Systemd was notified about stopping")
×
63
        }
×
64
}
65

66
// Notifier handles notifications about status of LND.
67
type Notifier struct {
68
        // notifiedReady remembers whether Ready was sent to avoid sending it
69
        // multiple times.
70
        notifiedReady bool
71
}
72

73
// NotifyReady notifies other applications that RPC is ready.
74
func (notifier *Notifier) NotifyReady(walletUnlocked bool) error {
×
75
        if !notifier.notifiedReady {
×
76
                err := systemdNotifyReady()
×
77
                if err != nil {
×
78
                        return err
×
79
                }
×
80
                notifier.notifiedReady = true
×
81
        }
82
        if walletUnlocked {
×
83
                _, _ = daemon.SdNotify(false, "STATUS=Wallet unlocked")
×
84
        } else {
×
85
                _, _ = daemon.SdNotify(false, "STATUS=Wallet locked")
×
86
        }
×
87

88
        return nil
×
89
}
90

91
// notifyStop notifies other applications that LND is stopping.
92
func (notifier *Notifier) notifyStop() {
×
93
        systemdNotifyStop()
×
94
}
×
95

96
// Interceptor contains channels and methods regarding application shutdown
97
// and interrupt signals.
98
type Interceptor struct {
99
        // interruptChannel is used to receive SIGINT (Ctrl+C) signals.
100
        interruptChannel chan os.Signal
101

102
        // shutdownChannel is closed once the main interrupt handler exits.
103
        shutdownChannel chan struct{}
104

105
        // shutdownRequestChannel is used to request the daemon to shutdown
106
        // gracefully, similar to when receiving SIGINT.
107
        shutdownRequestChannel chan struct{}
108

109
        // quit is closed when instructing the main interrupt handler to exit.
110
        // Note that to avoid losing notifications, only shutdown func may
111
        // close this channel.
112
        quit chan struct{}
113

114
        // Notifier handles sending shutdown notifications.
115
        Notifier Notifier
116
}
117

118
// Intercept starts the interception of interrupt signals and returns an `Interceptor` instance.
119
// Note that any previous active interceptor must be stopped before a new one can be created.
120
func Intercept() (Interceptor, error) {
×
121
        if !atomic.CompareAndSwapInt32(&started, 0, 1) {
×
122
                return Interceptor{}, errors.New("intercept already started")
×
123
        }
×
124

125
        channels := Interceptor{
×
126
                interruptChannel:       make(chan os.Signal, 1),
×
127
                shutdownChannel:        make(chan struct{}),
×
128
                shutdownRequestChannel: make(chan struct{}),
×
129
                quit:                   make(chan struct{}),
×
130
        }
×
131

×
132
        signalsToCatch := []os.Signal{
×
133
                os.Interrupt,
×
134
                os.Kill,
×
135
                syscall.SIGTERM,
×
136
                syscall.SIGQUIT,
×
137
        }
×
138
        signal.Notify(channels.interruptChannel, signalsToCatch...)
×
139
        go channels.mainInterruptHandler()
×
140

×
141
        return channels, nil
×
142
}
143

144
// mainInterruptHandler listens for SIGINT (Ctrl+C) signals on the
145
// interruptChannel and shutdown requests on the shutdownRequestChannel, and
146
// invokes the registered interruptCallbacks accordingly. It also listens for
147
// callback registration.
148
// It must be run as a goroutine.
149
func (c *Interceptor) mainInterruptHandler() {
×
150
        defer atomic.StoreInt32(&started, 0)
×
151
        // isShutdown is a flag which is used to indicate whether or not
×
152
        // the shutdown signal has already been received and hence any future
×
153
        // attempts to add a new interrupt handler should invoke them
×
154
        // immediately.
×
155
        var isShutdown bool
×
156

×
157
        // shutdown invokes the registered interrupt handlers, then signals the
×
158
        // shutdownChannel.
×
159
        shutdown := func() {
×
160
                // Ignore more than one shutdown signal.
×
161
                if isShutdown {
×
162
                        log.Infof("Already shutting down...")
×
163
                        return
×
164
                }
×
165
                isShutdown = true
×
166
                log.Infof("Shutting down...")
×
167
                c.Notifier.notifyStop()
×
168

×
169
                // Signal the main interrupt handler to exit, and stop accept
×
170
                // post-facto requests.
×
171
                close(c.quit)
×
172
        }
173

174
        for {
×
175
                select {
×
176
                case signal := <-c.interruptChannel:
×
177
                        log.Infof("Received %v", signal)
×
178
                        shutdown()
×
179

180
                case <-c.shutdownRequestChannel:
×
181
                        log.Infof("Received shutdown request.")
×
182
                        shutdown()
×
183

184
                case <-c.quit:
×
185
                        log.Infof("Gracefully shutting down.")
×
186
                        close(c.shutdownChannel)
×
187
                        signal.Stop(c.interruptChannel)
×
188
                        return
×
189
                }
190
        }
191
}
192

193
// Listening returns true if the main interrupt handler has been started, and
194
// has not been killed.
195
func (c *Interceptor) Listening() bool {
×
196
        // If our started field is not set, we are not yet listening for
×
197
        // interrupts.
×
198
        if atomic.LoadInt32(&started) != 1 {
×
199
                return false
×
200
        }
×
201

202
        // If we have started our main goroutine, we check whether we have
203
        // stopped it yet.
204
        return c.Alive()
×
205
}
206

207
// Alive returns true if the main interrupt handler has not been killed.
208
func (c *Interceptor) Alive() bool {
×
209
        select {
×
210
        case <-c.quit:
×
211
                return false
×
212
        default:
×
213
                return true
×
214
        }
215
}
216

217
// RequestShutdown initiates a graceful shutdown from the application.
218
func (c *Interceptor) RequestShutdown() {
×
219
        select {
×
220
        case c.shutdownRequestChannel <- struct{}{}:
×
221
        case <-c.quit:
×
222
        }
223
}
224

225
// ShutdownChannel returns the channel that will be closed once the main
226
// interrupt handler has exited.
227
func (c *Interceptor) ShutdownChannel() <-chan struct{} {
×
228
        return c.shutdownChannel
×
229
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc