diff --git a/docs/release-notes/release-notes-0.21.0.md b/docs/release-notes/release-notes-0.21.0.md index f81ba63dcce..3c19b3405c0 100644 --- a/docs/release-notes/release-notes-0.21.0.md +++ b/docs/release-notes/release-notes-0.21.0.md @@ -75,6 +75,11 @@ transitions during startup, avoiding lost unlocks during slow database initialization. +* [Ensure that the interceptor is properly closed on failure paths in `lnd.Start`](https://github.com/lightningnetwork/lnd/pull/10587). + This is mostly relevant for mobile and lnd-as-a-lib use-cases where subsequent + attempts to start lnd would otherwise fail with "intercept already started" + errors as the state stays in-process. + # New Features - Basic Support for [onion messaging forwarding](https://github.com/lightningnetwork/lnd/pull/9868) @@ -191,6 +196,7 @@ * Boris Nagaev * Elle Mouton * Erick Cestari +* Hampus Sjöberg * hieblmi * Matt Morehouse * Mohamed Awnallah diff --git a/lnd.go b/lnd.go index 76b08a114a1..9fc6591aec4 100644 --- a/lnd.go +++ b/lnd.go @@ -146,7 +146,7 @@ var errStreamIsolationWithProxySkip = errors.New( // This function starts all main system components then blocks until a signal // is received on the shutdownChan at which point everything is shut down again. func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, - interceptor signal.Interceptor) error { + interceptor signal.Interceptor) (mainErr error) { defer func() { ltndLog.Info("Shutdown complete") @@ -156,6 +156,17 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, } }() + // Make sure the signal interceptor is always fully stopped on any + // startup/runtime error so callers can safely retry startup. + defer func() { + if mainErr == nil { + return + } + + interceptor.RequestShutdown() + <-interceptor.ShutdownChannel() + }() + ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/lnd_main_test.go b/lnd_main_test.go new file mode 100644 index 00000000000..2e0f9066491 --- /dev/null +++ b/lnd_main_test.go @@ -0,0 +1,33 @@ +package lnd + +import ( + "net" + "testing" + "time" + + "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/signal" + "github.com/stretchr/testify/require" +) + +// TestMainErrorShutsDownInterceptor asserts that Main error paths always close +// the signal interceptor so startup can be retried in-process. +func TestMainErrorShutsDownInterceptor(t *testing.T) { + interceptor, err := signal.Intercept() + require.NoError(t, err) + + cfg := DefaultConfig() + cfg.Pprof = &lncfg.Pprof{} + cfg.RPCListeners = []net.Addr{ + &net.UnixAddr{Net: "invalid-network"}, + } + mainErr := Main(&cfg, ListenerCfg{}, nil, interceptor) + require.Error(t, mainErr) + + select { + case <-interceptor.ShutdownChannel(): + case <-time.After(5 * time.Second): + t.Fatalf("interceptor wasn't shut down after Main returned " + + "error") + } +}