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

lightningnetwork / lnd / 12583319996

02 Jan 2025 01:38PM UTC coverage: 57.522% (-1.1%) from 58.598%
12583319996

Pull #9361

github

starius
fn/ContextGuard: use context.AfterFunc to wait

Simplifies context cancellation handling by using context.AfterFunc instead of a
goroutine to wait for context cancellation. This approach avoids the overhead of
a goroutine during the waiting period.

For ctxQuitUnsafe, since g.quit is closed only in the Quit method (which also
cancels all associated contexts), waiting on context cancellation ensures the
same behavior without unnecessary dependency on g.quit.

Added a test to ensure that the Create method does not launch any goroutines.
Pull Request #9361: fn: optimize context guard

102587 of 178344 relevant lines covered (57.52%)

24734.33 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