lnd+signal: implement systemd notify

This adds support for notifying systemd about the state of LND. It
notifies systemd just before waiting for wallet password or, if
`wallet-password-file` was specified, right after unlocking the wallet.

This means that "ready" represents RPC being available for intended use.
It's intentional, so that client services can use `After=` in `systemd`
configuration to avoid misleading error messages about missing files or
refused connections.

Part of #4470
This commit is contained in:
Martin Habovstiak 2021-07-17 13:54:41 +02:00
parent 9e8b9ccd4c
commit 4bcb32753f
4 changed files with 96 additions and 2 deletions

View File

@ -25,6 +25,12 @@ for more information.
* [Stub code for interacting with `lnrpc` from a WASM context through JSON
messages was added](https://github.com/lightningnetwork/lnd/pull/5601).
* LND now [reports to systemd](https://github.com/lightningnetwork/lnd/pull/5536)
that RPC is ready (port bound, certificate generated, macaroons created,
in case of `wallet-unlock-password-file` wallet unlocked). This can be used to
avoid misleading error messages from dependent services if they use `After`
systemd option.
## Wallet
* It is now possible to fund a psbt [without specifying any

1
go.mod
View File

@ -14,6 +14,7 @@ require (
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0
github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec
github.com/btcsuite/btcwallet/wtxmgr v1.3.1-0.20210803004036-eebed51155ec
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
github.com/davecgh/go-spew v1.1.1
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-errors/errors v1.0.1

8
lnd.go
View File

@ -537,6 +537,10 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error
// to the default behavior of waiting for the wallet creation/unlocking
// over RPC.
default:
if err := interceptor.Notifier.NotifyReady(false); err != nil {
return err
}
params, err := waitForWalletPassword(
cfg, pwService, []btcwallet.LoaderOption{dbs.walletDB},
interceptor.ShutdownChannel(),
@ -893,6 +897,10 @@ func Main(cfg *Config, lisCfg ListenerCfg, interceptor signal.Interceptor) error
// We transition the RPC state to Active, as the RPC server is up.
interceptorChain.SetRPCActive()
if err := interceptor.Notifier.NotifyReady(true); err != nil {
return err
}
// If we're not in regtest or simnet mode, We'll wait until we're fully
// synced to continue the start up of the remainder of the daemon. This
// ensures that we don't accept any possibly invalid state transitions, or

View File

@ -7,10 +7,13 @@ package signal
import (
"errors"
"fmt"
"os"
"os/signal"
"sync/atomic"
"syscall"
"github.com/coreos/go-systemd/daemon"
)
var (
@ -19,8 +22,78 @@ var (
started int32
)
// systemdNotifyReady notifies systemd about LND being ready, logs the result of
// the operation or possible error. Besides logging, systemd being unavailable
// is ignored.
func systemdNotifyReady() error {
notified, err := daemon.SdNotify(false, daemon.SdNotifyReady)
if err != nil {
err := fmt.Errorf("failed to notify systemd %v (if you aren't "+
"running systemd clear the environment variable "+
"NOTIFY_SOCKET)", err)
log.Error(err)
// The SdNotify doc says it's common to ignore the
// error. We don't want to ignore it because if someone
// set up systemd to wait for initialization other
// processes would get stuck.
return err
}
if notified {
log.Info("Systemd was notified about our readiness")
} else {
log.Info("We're not running within systemd")
}
return nil
}
// systemdNotifyStop notifies systemd that LND is stopping and logs error if
// the notification failed. It also logs if the notification was actually sent.
// Systemd being unavailable is intentionally ignored.
func systemdNotifyStop() {
notified, err := daemon.SdNotify(false, daemon.SdNotifyStopping)
// Just log - we're stopping anyway.
if err != nil {
log.Errorf("Failed to notify systemd: %v", err)
}
if notified {
log.Infof("Systemd was notified about stopping")
}
}
// Notifier handles notifications about status of LND.
type Notifier struct {
// notifiedReady remembers whether Ready was sent to avoid sending it
// multiple times.
notifiedReady bool
}
// NotifyReady notifies other applications that RPC is ready.
func (notifier *Notifier) NotifyReady(walletUnlocked bool) error {
if !notifier.notifiedReady {
err := systemdNotifyReady()
if err != nil {
return err
}
notifier.notifiedReady = true
}
if walletUnlocked {
_, _ = daemon.SdNotify(false, "STATUS=Wallet unlocked")
} else {
_, _ = daemon.SdNotify(false, "STATUS=Wallet locked")
}
return nil
}
// notifyStop notifies other applications that LND is stopping.
func (notifier *Notifier) notifyStop() {
systemdNotifyStop()
}
// Interceptor contains channels and methods regarding application shutdown
// and interrupt signals
// and interrupt signals.
type Interceptor struct {
// interruptChannel is used to receive SIGINT (Ctrl+C) signals.
interruptChannel chan os.Signal
@ -33,11 +106,16 @@ type Interceptor struct {
shutdownRequestChannel chan struct{}
// quit is closed when instructing the main interrupt handler to exit.
// Note that to avoid losing notifications, only shutdown func may
// close this channel.
quit chan struct{}
// Notifier handles sending shutdown notifications.
Notifier Notifier
}
// Intercept starts the interception of interrupt signals and returns an `Interceptor` instance.
// Note that any previous active interceptor must be stopped before a new one can be created
// Note that any previous active interceptor must be stopped before a new one can be created.
func Intercept() (Interceptor, error) {
if !atomic.CompareAndSwapInt32(&started, 0, 1) {
return Interceptor{}, errors.New("intercept already started")
@ -85,6 +163,7 @@ func (c *Interceptor) mainInterruptHandler() {
}
isShutdown = true
log.Infof("Shutting down...")
c.Notifier.notifyStop()
// Signal the main interrupt handler to exit, and stop accept
// post-facto requests.