Skip to content

Commit bfbaed5

Browse files
committed
try to move to new runtime
1 parent 56d8bce commit bfbaed5

12 files changed

Lines changed: 1840 additions & 1811 deletions

File tree

examples/cli/src/main.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ enum Commands {
5858

5959
struct WalletState {
6060
wallet: Wallet,
61-
_runtime: Arc<Runtime>, // Keep runtime alive
6261
shutdown: Arc<AtomicBool>,
6362
}
6463

@@ -128,20 +127,20 @@ fn get_config(network: Network) -> Result<WalletConfig> {
128127
}
129128

130129
impl WalletState {
131-
async fn new(runtime: Arc<Runtime>) -> Result<Self> {
130+
async fn new() -> Result<Self> {
132131
let shutdown = Arc::new(AtomicBool::new(false));
133132
let config = get_config(NETWORK)
134133
.with_context(|| format!("Failed to get wallet config for network: {NETWORK:?}"))?;
135134

136135
println!("{} Initializing wallet...", "⚡".bright_yellow());
137136

138-
match Wallet::new_with_runtime(runtime.clone(), config).await {
137+
match Wallet::new(config).await {
139138
Ok(wallet) => {
140139
println!("{} Wallet initialized successfully!", "✅".bright_green());
141140
println!("Network: {}", NETWORK.to_string().bright_cyan());
142141

143142
let w = wallet.clone();
144-
runtime.spawn(async move {
143+
tokio::spawn(async move {
145144
let event = w.next_event_async().await;
146145
match event {
147146
Event::PaymentSuccessful { payment_id, .. } => {
@@ -198,7 +197,7 @@ impl WalletState {
198197
w.event_handled().unwrap();
199198
});
200199

201-
Ok(WalletState { wallet, _runtime: runtime, shutdown })
200+
Ok(WalletState { wallet, shutdown })
202201
},
203202
Err(e) => Err(anyhow::anyhow!("Failed to initialize wallet: {:?}", e)),
204203
}
@@ -213,7 +212,8 @@ impl WalletState {
213212
}
214213
}
215214

216-
fn main() -> Result<()> {
215+
#[tokio::main(flavor = "multi_thread")]
216+
async fn main() -> Result<()> {
217217
let cli = Cli::parse();
218218

219219
println!("{}", "🟠 Orange CLI Wallet".bright_yellow().bold());
@@ -224,7 +224,7 @@ fn main() -> Result<()> {
224224
let runtime = Arc::new(Runtime::new().context("Failed to create tokio runtime")?);
225225

226226
// Initialize wallet once at startup
227-
let mut state = runtime.block_on(WalletState::new(runtime.clone()))?;
227+
let mut state = runtime.block_on(WalletState::new())?;
228228

229229
// Set up signal handling for graceful shutdown
230230
let shutdown_state = state.shutdown.clone();

justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ default:
22
@just --list
33

44
test *args:
5-
cargo test {{ args }} --features _test-utils -p orange-sdk
5+
cargo test {{ args }} --features _test-utils -p orange-sdk -- --nocapture
66

77
test-cashu *args:
88
cargo test {{ args }} --features _cashu-tests -p orange-sdk

orange-sdk/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,6 @@ cdk-axum = { git = "https://github.com/benthecarman/cdk.git", rev = "0d7c1b2b9b7
3939
axum = { version = "0.8.1", optional = true }
4040

4141
uniffi = { version = "0.29", features = ["cli", "tokio"], optional = true }
42+
43+
[dev-dependencies]
44+
test-log = "0.2.18"

orange-sdk/src/ffi/orange/wallet.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,7 @@ impl Wallet {
9898

9999
let config: OrangeWalletConfig = config.try_into()?;
100100

101-
let rt_clone = rt.clone();
102-
let inner =
103-
rt.block_on(async move { OrangeWallet::new_with_runtime(rt_clone, config).await })?;
101+
let inner = rt.block_on(async move { OrangeWallet::new(config).await })?;
104102

105103
Ok(Wallet { inner: Arc::new(inner) })
106104
}

orange-sdk/src/lib.rs

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ use ldk_node::lightning_invoice::Bolt11Invoice;
4141
use ldk_node::payment::PaymentKind;
4242
use ldk_node::{BuildError, ChannelDetails, DynStore, NodeError};
4343

44-
use tokio::runtime::Runtime;
45-
4644
use std::collections::HashMap;
4745
use std::fmt::{self, Debug, Write};
4846
use std::sync::Arc;
@@ -54,6 +52,7 @@ mod ffi;
5452
mod lightning_wallet;
5553
pub(crate) mod logging;
5654
mod rebalancer;
55+
mod runtime;
5756
mod store;
5857
pub mod trusted_wallet;
5958

@@ -62,6 +61,7 @@ use logging::Logger;
6261
use trusted_wallet::TrustedError;
6362

6463
pub use crate::logging::LoggerType;
64+
use crate::runtime::Runtime;
6565
#[cfg(feature = "cashu")]
6666
pub use crate::trusted_wallet::cashu::CashuConfig;
6767
#[cfg(feature = "spark")]
@@ -510,27 +510,17 @@ impl Wallet {
510510
/// Recovery ensures trusted wallet funds can be restored when reconstructed from the same seed
511511
/// across different devices or installations.
512512
pub async fn new(config: WalletConfig) -> Result<Wallet, InitFailure> {
513-
let rt = tokio::runtime::Builder::new_multi_thread()
514-
.enable_all()
515-
.build()
516-
.map_err(|e| InitFailure::IoError(e.into()))?;
517-
518-
Self::new_with_runtime(Arc::new(rt), config).await
519-
}
520-
/// Constructs a new Wallet with a runtime.
521-
///
522-
/// `runtime` must be a reference to the running `tokio` runtime which we are currently
523-
/// operating in.
524-
// TODO: WOW that is a terrible API lol
525-
pub async fn new_with_runtime(
526-
runtime: Arc<Runtime>, config: WalletConfig,
527-
) -> Result<Wallet, InitFailure> {
528513
let tunables = config.tunables;
529514
let network = config.network;
530515
let logger = Arc::new(Logger::new(&config.logger_type).expect("Failed to open log file"));
531516

532517
log_info!(logger, "Initializing orange on network: {network}");
533518

519+
let runtime = Arc::new(Runtime::new(Arc::clone(&logger)).map_err(|e| {
520+
log_error!(logger, "Failed to set up tokio runtime: {e}");
521+
BuildError::RuntimeSetupFailed
522+
})?);
523+
534524
let store: Arc<DynStore> = match &config.storage_config {
535525
StorageConfig::LocalSQLite(path) => {
536526
Arc::new(SqliteStore::new(path.into(), Some("orange.sqlite".to_owned()), None)?)
@@ -623,7 +613,7 @@ impl Wallet {
623613
// Spawn a background thread that every second, we see if we should initiate a rebalance
624614
// This will withdraw from the trusted balance to our LN balance, possibly opening a channel.
625615
let rb = Arc::clone(&rebalancer);
626-
runtime.spawn(async move {
616+
runtime.spawn_cancellable_background_task(async move {
627617
loop {
628618
rb.do_rebalance_if_needed().await;
629619

@@ -1138,7 +1128,7 @@ impl Wallet {
11381128
},
11391129
);
11401130
let inner_ref = Arc::clone(&self.inner);
1141-
self.inner.runtime.spawn(async move {
1131+
self.inner.runtime.spawn_cancellable_background_task(async move {
11421132
inner_ref.rebalancer.do_rebalance_if_needed().await;
11431133
});
11441134
return Ok(());
@@ -1342,5 +1332,11 @@ impl Wallet {
13421332

13431333
log_debug!(self.inner.logger, "Stopping ln wallet...");
13441334
self.inner.ln_wallet.stop();
1335+
1336+
// Cancel cancellable background tasks
1337+
self.inner.runtime.abort_cancellable_background_tasks();
1338+
1339+
// Wait until non-cancellable background tasks (mod LDK's background processor) are done.
1340+
self.inner.runtime.wait_on_background_tasks();
13451341
}
13461342
}

orange-sdk/src/lightning_wallet.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::bitcoin::OutPoint;
22
use crate::event::{EventQueue, LdkEventHandler};
33
use crate::logging::Logger;
4+
use crate::runtime::Runtime;
45
use crate::store::{TxMetadataStore, TxStatus};
56
use crate::{ChainSource, InitFailure, PaymentType, Seed, WalletConfig};
67

@@ -27,7 +28,6 @@ use std::fmt::Debug;
2728
use std::pin::Pin;
2829
use std::sync::Arc;
2930

30-
use tokio::runtime::Runtime;
3131
use tokio::sync::watch;
3232

3333
#[derive(Debug, Clone, Copy)]
@@ -129,6 +129,8 @@ impl LightningWallet {
129129

130130
builder.set_custom_logger(Arc::clone(&logger) as Arc<dyn ldk_node::logger::LogWriter>);
131131

132+
builder.set_runtime(runtime.get_handle());
133+
132134
// download scorer and write to storage
133135
// todo switch to https://github.com/lightningdevkit/ldk-node/pull/449 once available
134136
if let Some(url) = config.scorer_url {
@@ -179,10 +181,10 @@ impl LightningWallet {
179181

180182
inner.ldk_node.start()?;
181183

182-
runtime.spawn(async move {
184+
runtime.spawn_cancellable_background_task(async move {
183185
loop {
184186
let event = ev_handler.ldk_node.next_event_async().await;
185-
log_debug!(ev_handler.logger, "Got ldk-node event {:?}", event);
187+
log_debug!(ev_handler.logger, "Got ldk-node event {event:?}");
186188
ev_handler.handle_ldk_node_event(event).await;
187189
}
188190
});

orange-sdk/src/runtime.rs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
use ldk_node::lightning::util::logger::Logger as _;
9+
use ldk_node::lightning::{log_debug, log_error, log_trace};
10+
use std::future::Future;
11+
use std::sync::{Arc, Mutex};
12+
use std::time::Duration;
13+
use tokio::task::{JoinHandle, JoinSet};
14+
15+
use crate::logging::Logger;
16+
17+
// The timeout after which we give up waiting on a background task to exit on shutdown.
18+
pub(crate) const BACKGROUND_TASK_SHUTDOWN_TIMEOUT_SECS: u64 = 5;
19+
20+
pub(crate) struct Runtime {
21+
mode: RuntimeMode,
22+
background_tasks: Mutex<JoinSet<()>>,
23+
cancellable_background_tasks: Mutex<JoinSet<()>>,
24+
logger: Arc<Logger>,
25+
}
26+
27+
impl Runtime {
28+
pub fn new(logger: Arc<Logger>) -> Result<Self, std::io::Error> {
29+
let mode = match tokio::runtime::Handle::try_current() {
30+
Ok(handle) => RuntimeMode::Handle(handle),
31+
Err(_) => {
32+
let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?;
33+
RuntimeMode::Owned(rt)
34+
},
35+
};
36+
let background_tasks = Mutex::new(JoinSet::new());
37+
let cancellable_background_tasks = Mutex::new(JoinSet::new());
38+
39+
Ok(Self { mode, background_tasks, cancellable_background_tasks, logger })
40+
}
41+
42+
pub fn get_handle(&self) -> tokio::runtime::Handle {
43+
match &self.mode {
44+
RuntimeMode::Owned(rt) => rt.handle().clone(),
45+
RuntimeMode::Handle(h) => h.clone(),
46+
}
47+
}
48+
49+
#[allow(unused)]
50+
pub fn with_handle(handle: tokio::runtime::Handle, logger: Arc<Logger>) -> Self {
51+
let mode = RuntimeMode::Handle(handle);
52+
let background_tasks = Mutex::new(JoinSet::new());
53+
let cancellable_background_tasks = Mutex::new(JoinSet::new());
54+
55+
Self { mode, background_tasks, cancellable_background_tasks, logger }
56+
}
57+
58+
pub fn spawn_background_task<F>(&self, future: F)
59+
where
60+
F: Future<Output = ()> + Send + 'static,
61+
{
62+
let mut background_tasks = self.background_tasks.lock().unwrap();
63+
let runtime_handle = self.handle();
64+
background_tasks.spawn_on(future, runtime_handle);
65+
}
66+
67+
pub fn spawn_cancellable_background_task<F>(&self, future: F)
68+
where
69+
F: Future<Output = ()> + Send + 'static,
70+
{
71+
let mut cancellable_background_tasks = self.cancellable_background_tasks.lock().unwrap();
72+
let runtime_handle = self.handle();
73+
cancellable_background_tasks.spawn_on(future, runtime_handle);
74+
}
75+
76+
#[allow(unused)]
77+
pub fn spawn_blocking<F, R>(&self, func: F) -> JoinHandle<R>
78+
where
79+
F: FnOnce() -> R + Send + 'static,
80+
R: Send + 'static,
81+
{
82+
let handle = self.handle();
83+
handle.spawn_blocking(func)
84+
}
85+
86+
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
87+
// While we generally decided not to overthink via which call graph users would enter our
88+
// runtime context, we'd still try to reuse whatever current context would be present
89+
// during `block_on`, as this is the context `block_in_place` would operate on. So we try
90+
// to detect the outer context here, and otherwise use whatever was set during
91+
// initialization.
92+
let handle = tokio::runtime::Handle::try_current().unwrap_or(self.handle().clone());
93+
tokio::task::block_in_place(move || handle.block_on(future))
94+
}
95+
96+
pub fn abort_cancellable_background_tasks(&self) {
97+
let mut tasks = core::mem::take(&mut *self.cancellable_background_tasks.lock().unwrap());
98+
debug_assert!(!tasks.is_empty(), "Expected some cancellable background_tasks");
99+
tasks.abort_all();
100+
self.block_on(async { while tasks.join_next().await.is_some() {} })
101+
}
102+
103+
pub fn wait_on_background_tasks(&self) {
104+
let mut tasks = core::mem::take(&mut *self.background_tasks.lock().unwrap());
105+
debug_assert!(!tasks.is_empty(), "Expected some background_tasks");
106+
self.block_on(async {
107+
loop {
108+
let timeout_fut = tokio::time::timeout(
109+
Duration::from_secs(BACKGROUND_TASK_SHUTDOWN_TIMEOUT_SECS),
110+
tasks.join_next_with_id(),
111+
);
112+
match timeout_fut.await {
113+
Ok(Some(Ok((id, _)))) => {
114+
log_trace!(self.logger, "Stopped background task with id {id}");
115+
},
116+
Ok(Some(Err(e))) => {
117+
tasks.abort_all();
118+
log_trace!(self.logger, "Stopping background task failed: {e}");
119+
break;
120+
},
121+
Ok(None) => {
122+
log_debug!(self.logger, "Stopped all background tasks");
123+
break;
124+
},
125+
Err(e) => {
126+
tasks.abort_all();
127+
log_error!(self.logger, "Stopping background task timed out: {e}");
128+
break;
129+
},
130+
}
131+
}
132+
})
133+
}
134+
135+
fn handle(&self) -> &tokio::runtime::Handle {
136+
match &self.mode {
137+
RuntimeMode::Owned(rt) => rt.handle(),
138+
RuntimeMode::Handle(handle) => handle,
139+
}
140+
}
141+
}
142+
143+
enum RuntimeMode {
144+
Owned(tokio::runtime::Runtime),
145+
Handle(tokio::runtime::Handle),
146+
}

0 commit comments

Comments
 (0)