From d308710d4f820e14eea68f95926bf1795a7bc843 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 16 Jan 2023 23:47:11 +0000 Subject: [PATCH 1/3] Use flexible timer types in background processor's regular jobs `background-processor` does a number of jobs on various timers. Currently, those are all done by checking the timers every 100ms by interrogating `std::time::Instant`. This is fine for the threaded version, but we'd like more flexibility in the `futures`- based `background-processor`. Here we swap the `std::time::Instant` interrogation for a lambda which we will switch out to the user-provided sleeper in the next commit. --- lightning-background-processor/src/lib.rs | 39 ++++++++++++----------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index ea24ef85e..e93139bf0 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -207,15 +207,15 @@ macro_rules! define_run_body { ($persister: ident, $chain_monitor: ident, $process_chain_monitor_events: expr, $channel_manager: ident, $process_channel_manager_events: expr, $gossip_sync: ident, $peer_manager: ident, $logger: ident, $scorer: ident, - $loop_exit_check: expr, $await: expr) + $loop_exit_check: expr, $await: expr, $get_timer: expr, $timer_elapsed: expr) => { { log_trace!($logger, "Calling ChannelManager's timer_tick_occurred on startup"); $channel_manager.timer_tick_occurred(); - let mut last_freshness_call = Instant::now(); - let mut last_ping_call = Instant::now(); - let mut last_prune_call = Instant::now(); - let mut last_scorer_persist_call = Instant::now(); + let mut last_freshness_call = $get_timer(FRESHNESS_TIMER); + let mut last_ping_call = $get_timer(PING_TIMER); + let mut last_prune_call = $get_timer(FIRST_NETWORK_PRUNE_TIMER); + let mut last_scorer_persist_call = $get_timer(SCORER_PERSIST_TIMER); let mut have_pruned = false; loop { @@ -237,9 +237,9 @@ macro_rules! define_run_body { // We wait up to 100ms, but track how long it takes to detect being put to sleep, // see `await_start`'s use below. - let await_start = Instant::now(); + let mut await_start = $get_timer(1); let updates_available = $await; - let await_time = await_start.elapsed(); + let await_slow = $timer_elapsed(&mut await_start, 1); if updates_available { log_trace!($logger, "Persisting ChannelManager..."); @@ -251,12 +251,12 @@ macro_rules! define_run_body { log_trace!($logger, "Terminating background processor."); break; } - if last_freshness_call.elapsed().as_secs() > FRESHNESS_TIMER { + if $timer_elapsed(&mut last_freshness_call, FRESHNESS_TIMER) { log_trace!($logger, "Calling ChannelManager's timer_tick_occurred"); $channel_manager.timer_tick_occurred(); - last_freshness_call = Instant::now(); + last_freshness_call = $get_timer(FRESHNESS_TIMER); } - if await_time > Duration::from_secs(1) { + if await_slow { // On various platforms, we may be starved of CPU cycles for several reasons. // E.g. on iOS, if we've been in the background, we will be entirely paused. // Similarly, if we're on a desktop platform and the device has been asleep, we @@ -271,18 +271,18 @@ macro_rules! define_run_body { // peers. log_trace!($logger, "100ms sleep took more than a second, disconnecting peers."); $peer_manager.disconnect_all_peers(); - last_ping_call = Instant::now(); - } else if last_ping_call.elapsed().as_secs() > PING_TIMER { + last_ping_call = $get_timer(PING_TIMER); + } else if $timer_elapsed(&mut last_ping_call, PING_TIMER) { log_trace!($logger, "Calling PeerManager's timer_tick_occurred"); $peer_manager.timer_tick_occurred(); - last_ping_call = Instant::now(); + last_ping_call = $get_timer(PING_TIMER); } // Note that we want to run a graph prune once not long after startup before // falling back to our usual hourly prunes. This avoids short-lived clients never // pruning their network graph. We run once 60 seconds after startup before // continuing our normal cadence. - if last_prune_call.elapsed().as_secs() > if have_pruned { NETWORK_PRUNE_TIMER } else { FIRST_NETWORK_PRUNE_TIMER } { + if $timer_elapsed(&mut last_prune_call, if have_pruned { NETWORK_PRUNE_TIMER } else { FIRST_NETWORK_PRUNE_TIMER }) { // The network graph must not be pruned while rapid sync completion is pending if let Some(network_graph) = $gossip_sync.prunable_network_graph() { log_trace!($logger, "Pruning and persisting network graph."); @@ -292,19 +292,19 @@ macro_rules! define_run_body { log_error!($logger, "Error: Failed to persist network graph, check your disk and permissions {}", e) } - last_prune_call = Instant::now(); + last_prune_call = $get_timer(NETWORK_PRUNE_TIMER); have_pruned = true; } } - if last_scorer_persist_call.elapsed().as_secs() > SCORER_PERSIST_TIMER { + if $timer_elapsed(&mut last_scorer_persist_call, SCORER_PERSIST_TIMER) { if let Some(ref scorer) = $scorer { log_trace!($logger, "Persisting scorer"); if let Err(e) = $persister.persist_scorer(&scorer) { log_error!($logger, "Error: Failed to persist scorer, check your disk and permissions {}", e) } } - last_scorer_persist_call = Instant::now(); + last_scorer_persist_call = $get_timer(SCORER_PERSIST_TIMER); } } @@ -411,7 +411,7 @@ where false } } - }) + }, |_| Instant::now(), |time: &Instant, dur| time.elapsed().as_secs() > dur) } impl BackgroundProcessor { @@ -522,7 +522,8 @@ impl BackgroundProcessor { define_run_body!(persister, chain_monitor, chain_monitor.process_pending_events(&event_handler), channel_manager, channel_manager.process_pending_events(&event_handler), gossip_sync, peer_manager, logger, scorer, stop_thread.load(Ordering::Acquire), - channel_manager.await_persistable_update_timeout(Duration::from_millis(100))) + channel_manager.await_persistable_update_timeout(Duration::from_millis(100)), + |_| Instant::now(), |time: &Instant, dur| time.elapsed().as_secs() > dur) }); Self { stop_thread: stop_thread_clone, thread_handle: Some(handle) } } From e59b3847a363afba5d30e1f1271341e3163f7591 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Tue, 17 Jan 2023 00:03:43 +0000 Subject: [PATCH 2/3] Use the user-provided `SleepFuture` for interval checks in BP `background-processor` does a number of jobs on various timers. Instead of doing those by interrogating `std::time::Instant`, this change swaps to using the existing user-provided sleep future. Fixes #1864. --- lightning-background-processor/src/lib.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index e93139bf0..b2ee86547 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -36,7 +36,7 @@ use std::time::{Duration, Instant}; use std::ops::Deref; #[cfg(feature = "futures")] -use futures_util::{select_biased, future::FutureExt}; +use futures_util::{select_biased, future::FutureExt, task}; /// `BackgroundProcessor` takes care of tasks that (1) need to happen periodically to keep /// Rust-Lightning running properly, and (2) either can or should be run in the background. Its @@ -364,7 +364,7 @@ pub async fn process_events_async< PM: 'static + Deref> + Send + Sync, S: 'static + Deref + Send + Sync, SC: WriteableScore<'a>, - SleepFuture: core::future::Future, + SleepFuture: core::future::Future + core::marker::Unpin, Sleeper: Fn(Duration) -> SleepFuture >( persister: PS, event_handler: EventHandler, chain_monitor: M, channel_manager: CM, @@ -411,7 +411,12 @@ where false } } - }, |_| Instant::now(), |time: &Instant, dur| time.elapsed().as_secs() > dur) + }, |t| sleeper(Duration::from_secs(t)), + |fut: &mut SleepFuture, _| { + let mut waker = task::noop_waker(); + let mut ctx = task::Context::from_waker(&mut waker); + core::pin::Pin::new(fut).poll(&mut ctx).is_ready() + }) } impl BackgroundProcessor { From aa4c6f6418d7500307853d125528d65e9170cb15 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Tue, 17 Jan 2023 00:16:48 +0000 Subject: [PATCH 3/3] Make `background-processor` no-std-friendly (ish) This makes `background-processor` build without `std` at all. This isn't particularly useful in the general no-std case as `background-processor` is only useful with the `futures` feature, and async will generally need `std` in some way or another. Still, it ensures we don't end up reintroducing a dependency on the current time, which breaks `wasm` use-cases. --- lightning-background-processor/Cargo.toml | 9 +++-- lightning-background-processor/src/lib.rs | 44 ++++++++++++++++++----- no-std-check/Cargo.toml | 1 + 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/lightning-background-processor/Cargo.toml b/lightning-background-processor/Cargo.toml index 343408ea9..96ac33a91 100644 --- a/lightning-background-processor/Cargo.toml +++ b/lightning-background-processor/Cargo.toml @@ -15,11 +15,14 @@ rustdoc-args = ["--cfg", "docsrs"] [features] futures = [ "futures-util" ] +std = ["lightning/std", "lightning-rapid-gossip-sync/std"] + +default = ["std"] [dependencies] -bitcoin = "0.29.0" -lightning = { version = "0.0.113", path = "../lightning", features = ["std"] } -lightning-rapid-gossip-sync = { version = "0.0.113", path = "../lightning-rapid-gossip-sync" } +bitcoin = { version = "0.29.0", default-features = false } +lightning = { version = "0.0.113", path = "../lightning", default-features = false } +lightning-rapid-gossip-sync = { version = "0.0.113", path = "../lightning-rapid-gossip-sync", default-features = false } futures-util = { version = "0.3", default-features = false, features = ["async-await-macro"], optional = true } [dev-dependencies] diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index b2ee86547..60a63d853 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -11,6 +11,11 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(all(not(feature = "std"), not(test)), no_std)] + +#[cfg(any(test, feature = "std"))] +extern crate core; + #[macro_use] extern crate lightning; extern crate lightning_rapid_gossip_sync; @@ -28,12 +33,19 @@ use lightning::util::events::{Event, EventHandler, EventsProvider}; use lightning::util::logger::Logger; use lightning::util::persist::Persister; use lightning_rapid_gossip_sync::RapidGossipSync; +use lightning::io; + +use core::ops::Deref; +use core::time::Duration; + +#[cfg(feature = "std")] use std::sync::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::thread; -use std::thread::JoinHandle; -use std::time::{Duration, Instant}; -use std::ops::Deref; +#[cfg(feature = "std")] +use core::sync::atomic::{AtomicBool, Ordering}; +#[cfg(feature = "std")] +use std::thread::{self, JoinHandle}; +#[cfg(feature = "std")] +use std::time::Instant; #[cfg(feature = "futures")] use futures_util::{select_biased, future::FutureExt, task}; @@ -62,6 +74,7 @@ use futures_util::{select_biased, future::FutureExt, task}; /// /// [`ChannelMonitor`]: lightning::chain::channelmonitor::ChannelMonitor /// [`Event`]: lightning::util::events::Event +#[cfg(feature = "std")] #[must_use = "BackgroundProcessor will immediately stop on drop. It should be stored until shutdown."] pub struct BackgroundProcessor { stop_thread: Arc, @@ -285,8 +298,14 @@ macro_rules! define_run_body { if $timer_elapsed(&mut last_prune_call, if have_pruned { NETWORK_PRUNE_TIMER } else { FIRST_NETWORK_PRUNE_TIMER }) { // The network graph must not be pruned while rapid sync completion is pending if let Some(network_graph) = $gossip_sync.prunable_network_graph() { - log_trace!($logger, "Pruning and persisting network graph."); - network_graph.remove_stale_channels_and_tracking(); + #[cfg(feature = "std")] { + log_trace!($logger, "Pruning and persisting network graph."); + network_graph.remove_stale_channels_and_tracking(); + } + #[cfg(not(feature = "std"))] { + log_warn!($logger, "Not pruning network graph, consider enabling `std` or doing so manually with remove_stale_channels_and_tracking_with_time."); + log_trace!($logger, "Persisting network graph."); + } if let Err(e) = $persister.persist_graph(network_graph) { log_error!($logger, "Error: Failed to persist network graph, check your disk and permissions {}", e) @@ -334,6 +353,11 @@ macro_rules! define_run_body { /// future which outputs true, the loop will exit and this function's future will complete. /// /// See [`BackgroundProcessor::start`] for information on which actions this handles. +/// +/// Requires the `futures` feature. Note that while this method is available without the `std` +/// feature, doing so will skip calling [`NetworkGraph::remove_stale_channels_and_tracking`], +/// you should call [`NetworkGraph::remove_stale_channels_and_tracking_with_time`] regularly +/// manually instead. #[cfg(feature = "futures")] pub async fn process_events_async< 'a, @@ -370,7 +394,7 @@ pub async fn process_events_async< persister: PS, event_handler: EventHandler, chain_monitor: M, channel_manager: CM, gossip_sync: GossipSync, peer_manager: PM, logger: L, scorer: Option, sleeper: Sleeper, -) -> Result<(), std::io::Error> +) -> Result<(), io::Error> where CA::Target: 'static + chain::Access, CF::Target: 'static + chain::Filter, @@ -419,6 +443,7 @@ where }) } +#[cfg(feature = "std")] impl BackgroundProcessor { /// Start a background thread that takes care of responsibilities enumerated in the [top-level /// documentation]. @@ -574,13 +599,14 @@ impl BackgroundProcessor { } } +#[cfg(feature = "std")] impl Drop for BackgroundProcessor { fn drop(&mut self) { self.stop_and_join_thread().unwrap(); } } -#[cfg(test)] +#[cfg(all(feature = "std", test))] mod tests { use bitcoin::blockdata::block::BlockHeader; use bitcoin::blockdata::constants::genesis_block; diff --git a/no-std-check/Cargo.toml b/no-std-check/Cargo.toml index 947838633..cb22dcea9 100644 --- a/no-std-check/Cargo.toml +++ b/no-std-check/Cargo.toml @@ -10,3 +10,4 @@ default = ["lightning/no-std", "lightning-invoice/no-std", "lightning-rapid-goss lightning = { path = "../lightning", default-features = false } lightning-invoice = { path = "../lightning-invoice", default-features = false } lightning-rapid-gossip-sync = { path = "../lightning-rapid-gossip-sync", default-features = false } +lightning-background-processor = { path = "../lightning-background-processor", features = ["futures"], default-features = false }