From 2a6b775f607dc0b56be874807bd36427922ac681 Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Tue, 22 Aug 2023 14:19:24 +0200 Subject: [PATCH] lib: non-blocking daemon shutdown This makes it possible to trigger the shutdown of the daemon through the DaemonHandle, without having to block while waiting for the poller thread to join. Incidently, this allows to avoid having to move `self` which in turns allows to fix a GUI bug (see https://github.com/wizardsardine/liana/issues/622). Only available as an optional feature since `is_finished` needs rustc 1.61. --- Cargo.toml | 1 + src/bitcoin/poller/mod.rs | 14 +++++++++++++- src/lib.rs | 14 +++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d98e19e6..ff8da5ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ required-features = ["daemon"] [features] default = ["daemon"] daemon = ["libc"] +nonblocking_shutdown = [] [dependencies] # For managing transactions (it re-exports the bitcoin crate) diff --git a/src/bitcoin/poller/mod.rs b/src/bitcoin/poller/mod.rs index 95a20535..7731af2e 100644 --- a/src/bitcoin/poller/mod.rs +++ b/src/bitcoin/poller/mod.rs @@ -36,11 +36,23 @@ impl Poller { Poller { shutdown, handle } } - pub fn stop(self) { + pub fn trigger_stop(&self) { self.shutdown.store(true, atomic::Ordering::Relaxed); + } + + pub fn stop(self) { + self.trigger_stop(); self.handle.join().expect("The poller loop must not fail"); } + #[cfg(feature = "nonblocking_shutdown")] + pub fn is_stopped(&self) -> bool { + // Doc says "This might return true for a brief moment after the thread’s main function has + // returned, but before the thread itself has stopped running.". But it's not an issue for + // us, as long as the main poller function has returned we are good. + self.handle.is_finished() + } + #[cfg(test)] pub fn test_stop(&mut self) { self.shutdown.store(true, atomic::Ordering::Relaxed); diff --git a/src/lib.rs b/src/lib.rs index be6e1cf2..fdea27cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -458,12 +458,24 @@ impl DaemonHandle { Ok(()) } - // NOTE: this moves out the data as it should not be reused after shutdown /// Shut down the Liana daemon. pub fn shutdown(self) { self.bitcoin_poller.stop(); } + /// Tell the daemon to shut down. This will return before the shutdown completes. The structure + /// must not be reused after triggering shutdown. + #[cfg(feature = "nonblocking_shutdown")] + pub fn trigger_shutdown(&self) { + self.bitcoin_poller.trigger_stop() + } + + /// Whether the daemon has finished shutting down. + #[cfg(feature = "nonblocking_shutdown")] + pub fn shutdown_complete(&self) -> bool { + self.bitcoin_poller.is_stopped() + } + // We need a shutdown utility that does not move for implementing Drop for the DummyLiana #[cfg(test)] pub fn test_shutdown(&mut self) {