Skip to content
This repository was archived by the owner on Aug 31, 2023. It is now read-only.

Commit d895a5b

Browse files
committed
chore: Introduce dedicate runtime
Annotating the function with `#[tokio::main]` killed the runtime after the function completes. Thus we had to block the run function indefinitely for the long living threads to keep going. This change introduces a runtime managed outside of the function scope and thus allows the `run` function to return bringing the following advantages. - We don't block a whole frb worker thread just to run the lightning node, sync tasks, background processor, etc. - We are using a multi threaded runtime instead of the current thread - allowing to actually join the background processor without blocking all other tasks. - making better use of multiple cpu cores. - We are not creating a new runtime on every async bridge call.
1 parent 1a19388 commit d895a5b

5 files changed

Lines changed: 162 additions & 169 deletions

File tree

rust/src/api.rs

Lines changed: 129 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use crate::offer::Offer;
1313
use crate::wallet;
1414
use crate::wallet::Balance;
1515
use crate::wallet::LightningTransaction;
16-
use anyhow::anyhow;
1716
use anyhow::Context;
1817
use anyhow::Result;
1918
use flutter_rust_bridge::StreamSink;
@@ -22,13 +21,14 @@ use lightning_invoice::Invoice;
2221
use lightning_invoice::InvoiceDescription;
2322
use rust_decimal::prelude::ToPrimitive;
2423
use rust_decimal::Decimal;
24+
use state::Storage;
2525
use std::ops::Add;
2626
use std::path::Path;
2727
use std::str::FromStr;
2828
use std::time::SystemTime;
2929
use time::Duration;
3030
pub use time::OffsetDateTime;
31-
use tokio::try_join;
31+
use tokio::runtime::Runtime;
3232

3333
pub struct Address {
3434
pub address: String,
@@ -168,91 +168,114 @@ impl WalletInfo {
168168
Ok(tx_history)
169169
}
170170
}
171+
/// Lazily creates a multi threaded runtime with the the number of worker threads corresponding to
172+
/// the number of available cores.
173+
fn runtime() -> Result<&'static Runtime> {
174+
static RUNTIME: Storage<Runtime> = Storage::new();
171175

172-
#[tokio::main(flavor = "current_thread")]
173-
pub async fn refresh_wallet_info() -> Result<WalletInfo> {
174-
wallet::sync()?;
175-
WalletInfo::build_wallet_info().await
176+
if RUNTIME.try_get().is_none() {
177+
let runtime = Runtime::new()?;
178+
RUNTIME.set(runtime);
179+
}
180+
181+
Ok(RUNTIME.get())
182+
}
183+
184+
pub fn refresh_wallet_info() -> Result<WalletInfo> {
185+
runtime()?.block_on(async {
186+
wallet::sync()?;
187+
WalletInfo::build_wallet_info().await
188+
})
176189
}
177190

178-
#[tokio::main(flavor = "current_thread")]
179-
pub async fn run(stream: StreamSink<Event>, app_dir: String) -> Result<()> {
191+
pub fn run(stream: StreamSink<Event>, app_dir: String) -> Result<()> {
180192
let network = config::network();
181193
anyhow::ensure!(!app_dir.is_empty(), "app_dir must not be empty");
182-
stream.add(Event::Init(format!("Initialising {network} wallet")));
183-
wallet::init_wallet(Path::new(app_dir.as_str()))?;
184-
185-
stream.add(Event::Init("Initialising database".to_string()));
186-
db::init_db(
187-
&Path::new(app_dir.as_str())
188-
.join(network.to_string())
189-
.join("taker.sqlite"),
190-
)
191-
.await?;
192-
193-
stream.add(Event::Init("Starting full ldk node".to_string()));
194-
let background_processor = wallet::run_ldk().await?;
195-
196-
stream.add(Event::Init("Fetching an offer".to_string()));
197-
stream.add(Event::Offer(offer::get_offer().await.ok()));
198-
199-
stream.add(Event::Init("Fetching your balance".to_string()));
200-
stream.add(Event::WalletInfo(
201-
WalletInfo::build_wallet_info().await.ok(),
202-
));
203-
stream.add(Event::Init("Checking channel state".to_string()));
204-
stream.add(Event::ChannelState(get_channel_state()));
205-
206-
stream.add(Event::Init("Ready".to_string()));
207-
stream.add(Event::Ready);
208-
209-
// spawn a connection task keeping the connection with the maker alive.
210-
let peer_manager = wallet::get_peer_manager()?;
211-
let connection_handle = connection::spawn(peer_manager);
212-
213-
// sync offers every 5 seconds
214-
let offer_handle = offer::spawn(stream.clone());
215-
216-
// sync wallet every 60 seconds
217-
let wallet_sync_handle = tokio::spawn(async {
218-
loop {
219-
wallet::sync().unwrap_or_else(|e| tracing::error!(?e, "Failed to sync wallet"));
220-
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
221-
}
222-
});
223-
224-
// sync wallet info every 10 seconds
225-
let wallet_info_stream = stream.clone();
226-
let wallet_info_sync_handle = tokio::spawn(async move {
227-
loop {
228-
match WalletInfo::build_wallet_info().await {
229-
Ok(wallet_info) => {
230-
let _ = wallet_info_stream.add(Event::WalletInfo(Some(wallet_info)));
194+
let runtime = runtime()?;
195+
runtime.block_on(async move {
196+
stream.add(Event::Init(format!("Initialising {network} wallet")));
197+
wallet::init_wallet(Path::new(app_dir.as_str()))?;
198+
199+
stream.add(Event::Init("Initialising database".to_string()));
200+
db::init_db(
201+
&Path::new(app_dir.as_str())
202+
.join(network.to_string())
203+
.join("taker.sqlite"),
204+
)
205+
.await?;
206+
207+
stream.add(Event::Init("Starting full ldk node".to_string()));
208+
let background_processor = wallet::run_ldk()?;
209+
210+
stream.add(Event::Init("Fetching an offer".to_string()));
211+
stream.add(Event::Offer(offer::get_offer().await.ok()));
212+
213+
stream.add(Event::Init("Fetching your balance".to_string()));
214+
stream.add(Event::WalletInfo(
215+
WalletInfo::build_wallet_info().await.ok(),
216+
));
217+
stream.add(Event::Init("Checking channel state".to_string()));
218+
stream.add(Event::ChannelState(get_channel_state()));
219+
220+
stream.add(Event::Init("Ready".to_string()));
221+
stream.add(Event::Ready);
222+
223+
// spawn a connection task keeping the connection with the maker alive.
224+
runtime.spawn(async move {
225+
let peer_info = config::maker_peer_info();
226+
loop {
227+
let peer_manager = wallet::get_peer_manager();
228+
connection::connect(peer_manager, peer_info).await;
229+
// add a delay before retrying to connect
230+
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
231+
}
232+
});
233+
234+
let offer_stream = stream.clone();
235+
runtime.spawn(async move {
236+
loop {
237+
offer_stream.add(Event::Offer(offer::get_offer().await.ok()));
238+
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
239+
}
240+
});
241+
242+
runtime.spawn(async {
243+
loop {
244+
wallet::sync().unwrap_or_else(|e| tracing::error!(?e, "Failed to sync wallet"));
245+
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
246+
}
247+
});
248+
249+
let wallet_info_stream = stream.clone();
250+
runtime.spawn(async move {
251+
loop {
252+
match WalletInfo::build_wallet_info().await {
253+
Ok(wallet_info) => {
254+
let _ = wallet_info_stream.add(Event::WalletInfo(Some(wallet_info)));
255+
}
256+
Err(e) => tracing::error!(?e, "Failed to build wallet info"),
231257
}
232-
Err(e) => tracing::error!(?e, "Failed to build wallet info"),
258+
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
233259
}
234-
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
235-
}
236-
});
237-
238-
// sync channel state every 5 seconds
239-
let channel_state_stream = stream.clone();
240-
let channel_state_handle = tokio::spawn(async move {
241-
loop {
242-
channel_state_stream.add(Event::ChannelState(get_channel_state()));
243-
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
244-
}
245-
});
260+
});
246261

247-
try_join!(
248-
connection_handle,
249-
offer_handle,
250-
wallet_sync_handle,
251-
wallet_info_sync_handle,
252-
channel_state_handle,
253-
)?;
262+
let channel_state_stream = stream.clone();
263+
runtime.spawn(async move {
264+
loop {
265+
channel_state_stream.add(Event::ChannelState(get_channel_state()));
266+
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
267+
}
268+
});
254269

255-
background_processor.join().map_err(|e| anyhow!(e))
270+
runtime.spawn_blocking(move || {
271+
// background processor joins on a sync thread, meaning that join here will block a
272+
// full thread, which is dis-encouraged to do in async code.
273+
if let Err(err) = background_processor.join() {
274+
tracing::error!(?err, "Background processor stopped unexpected");
275+
}
276+
});
277+
Ok(())
278+
})
256279
}
257280

258281
pub fn get_balance() -> Result<Balance> {
@@ -279,16 +302,18 @@ pub fn network() -> SyncReturn<String> {
279302
SyncReturn(config::network().to_string())
280303
}
281304

282-
#[tokio::main(flavor = "current_thread")]
283-
pub async fn open_channel(taker_amount: u64) -> Result<()> {
284-
let peer_info = config::maker_peer_info();
285-
wallet::open_channel(peer_info, taker_amount).await
305+
pub fn open_channel(taker_amount: u64) -> Result<()> {
306+
runtime()?.block_on(async {
307+
let peer_info = config::maker_peer_info();
308+
wallet::open_channel(peer_info, taker_amount).await
309+
})
286310
}
287311

288-
#[tokio::main(flavor = "current_thread")]
289-
pub async fn close_channel() -> Result<()> {
290-
let peer_info = config::maker_peer_info();
291-
wallet::close_channel(peer_info.pubkey, false).await
312+
pub fn close_channel() -> Result<()> {
313+
runtime()?.block_on(async {
314+
let peer_info = config::maker_peer_info();
315+
wallet::close_channel(peer_info.pubkey, false).await
316+
})
292317
}
293318

294319
pub fn send_to_address(address: String, amount: u64) -> Result<String> {
@@ -301,42 +326,32 @@ pub fn send_to_address(address: String, amount: u64) -> Result<String> {
301326
Ok(txid)
302327
}
303328

304-
#[tokio::main(flavor = "current_thread")]
305-
pub async fn list_cfds() -> Result<Vec<Cfd>> {
306-
let mut conn = db::acquire().await?;
307-
cfd::load_cfds(&mut conn).await
329+
pub fn list_cfds() -> Result<Vec<Cfd>> {
330+
runtime()?.block_on(async {
331+
let mut conn = db::acquire().await?;
332+
cfd::load_cfds(&mut conn).await
333+
})
308334
}
309335

310-
#[tokio::main(flavor = "current_thread")]
311-
pub async fn open_cfd(order: Order) -> Result<()> {
312-
cfd::open(&order).await
336+
pub fn open_cfd(order: Order) -> Result<()> {
337+
runtime()?.block_on(async { cfd::open(&order).await })
313338
}
314339

315-
#[tokio::main(flavor = "current_thread")]
316-
pub async fn call_faucet(address: String) -> Result<String> {
340+
pub fn call_faucet(address: String) -> Result<String> {
317341
anyhow::ensure!(
318342
!address.is_empty(),
319343
"Cannot call faucet because of empty address"
320344
);
321-
faucet::call_faucet(address).await
345+
runtime()?.block_on(async { faucet::call_faucet(address).await })
322346
}
323347

324-
#[tokio::main(flavor = "current_thread")]
325-
pub async fn get_fee_recommendation() -> Result<u32> {
326-
let fee_recommendation = wallet::get_fee_recommendation()?;
327-
328-
Ok(fee_recommendation)
348+
pub fn get_fee_recommendation() -> Result<u32> {
349+
wallet::get_fee_recommendation()
329350
}
330351

331352
/// Settles a CFD with the given taker and maker amounts in sats
332-
#[tokio::main(flavor = "current_thread")]
333-
pub async fn settle_cfd(cfd: Cfd, offer: Offer) -> Result<()> {
334-
cfd::settle(&cfd, &offer).await
335-
}
336-
337-
#[tokio::main(flavor = "current_thread")]
338-
pub async fn get_lightning_tx_history() -> Result<Vec<LightningTransaction>> {
339-
wallet::get_lightning_history().await
353+
pub fn settle_cfd(cfd: Cfd, offer: Offer) -> Result<()> {
354+
runtime()?.block_on(async { cfd::settle(&cfd, &offer).await })
340355
}
341356

342357
/// Initialise logging infrastructure for Rust
@@ -350,19 +365,18 @@ pub fn get_seed_phrase() -> Vec<String> {
350365
wallet::get_seed_phrase()
351366
}
352367

353-
#[tokio::main(flavor = "current_thread")]
354-
pub async fn send_lightning_payment(invoice: String) -> Result<()> {
368+
pub fn send_lightning_payment(invoice: String) -> Result<()> {
355369
anyhow::ensure!(!invoice.is_empty(), "Cannot pay empty invoice");
356-
wallet::send_lightning_payment(&invoice).await
370+
runtime()?.block_on(async { wallet::send_lightning_payment(&invoice).await })
357371
}
358372

359-
#[tokio::main(flavor = "current_thread")]
360-
pub async fn create_lightning_invoice(
373+
pub fn create_lightning_invoice(
361374
amount_sats: u64,
362375
expiry_secs: u32,
363376
description: String,
364377
) -> Result<String> {
365-
wallet::create_invoice(amount_sats, expiry_secs, description).await
378+
runtime()?
379+
.block_on(async { wallet::create_invoice(amount_sats, expiry_secs, description).await })
366380
}
367381

368382
// Note, this implementation has to be on the api level as otherwise it wouldn't be generated

rust/src/connection.rs

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,36 @@
1-
use crate::config;
1+
use crate::lightning::PeerInfo;
22
use crate::lightning::PeerManager;
33
use bdk::bitcoin::secp256k1::PublicKey;
44
use std::sync::Arc;
55
use std::time::Duration;
6-
use tokio::task::JoinHandle;
76

8-
pub fn spawn(peer_manager: Arc<PeerManager>) -> JoinHandle<()> {
9-
// keep connection with maker alive!
10-
tokio::spawn(async move {
11-
let peer_info = config::maker_peer_info();
12-
loop {
13-
tracing::info!("Connecting to {peer_info}");
14-
match lightning_net_tokio::connect_outbound(
15-
Arc::clone(&peer_manager),
16-
peer_info.pubkey,
17-
peer_info.peer_addr,
18-
)
19-
.await
20-
{
21-
Some(connection_closed_future) => {
22-
let mut connection_closed_future = Box::pin(connection_closed_future);
23-
while !is_connected(&peer_manager, peer_info.pubkey) {
24-
if futures::poll!(&mut connection_closed_future).is_ready() {
25-
tracing::warn!("Peer disconnected before we finished the handshake! Retrying in 5 seconds.");
26-
tokio::time::sleep(Duration::from_secs(5)).await;
27-
return;
28-
}
29-
tokio::time::sleep(Duration::from_secs(5)).await;
30-
}
31-
tracing::info!("Successfully connected to {peer_info}");
32-
connection_closed_future.await;
33-
tracing::warn!("Lost connection to maker, retrying immediately.")
34-
}
35-
None => {
36-
tracing::warn!("Failed to connect to maker! Retrying in 5 seconds.");
7+
pub async fn connect(peer_manager: Arc<PeerManager>, peer_info: PeerInfo) {
8+
tracing::info!("Connecting to {peer_info}");
9+
match lightning_net_tokio::connect_outbound(
10+
Arc::clone(&peer_manager),
11+
peer_info.pubkey,
12+
peer_info.peer_addr,
13+
)
14+
.await
15+
{
16+
Some(connection_closed_future) => {
17+
let mut connection_closed_future = Box::pin(connection_closed_future);
18+
while !is_connected(&peer_manager, peer_info.pubkey) {
19+
if futures::poll!(&mut connection_closed_future).is_ready() {
20+
tracing::warn!("Peer disconnected before we finished the handshake! Retrying in 5 seconds.");
3721
tokio::time::sleep(Duration::from_secs(5)).await;
22+
return;
3823
}
24+
tokio::time::sleep(Duration::from_secs(5)).await;
3925
}
26+
tracing::info!("Successfully connected to {peer_info}");
27+
connection_closed_future.await;
28+
tracing::warn!("Lost connection to maker, retrying immediately.")
29+
}
30+
None => {
31+
tracing::warn!("Failed to connect to maker! Retrying.");
4032
}
41-
})
33+
}
4234
}
4335

4436
fn is_connected(peer_manager: &Arc<PeerManager>, pubkey: PublicKey) -> bool {

0 commit comments

Comments
 (0)