Skip to content

Commit 90e5b57

Browse files
committed
feat(async)!: add Sleeper trait to allow custom runtime
1 parent db8acc0 commit 90e5b57

File tree

3 files changed

+63
-6
lines changed

3 files changed

+63
-6
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ serde_json = "1.0"
3030
tokio = { version = "1.41.0", features = ["full"] }
3131
electrsd = { version = "0.28.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_25_0"] }
3232
lazy_static = "1.4.0"
33+
async-std = { version = "1.13.0"}
3334

3435
[features]
3536
default = ["blocking", "async", "async-https", "tokio"]

src/async.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! Esplora by way of `reqwest` HTTP client.
1313
1414
use std::collections::HashMap;
15+
use std::marker::PhantomData;
1516
use std::str::FromStr;
1617

1718
use bitcoin::consensus::{deserialize, serialize, Decodable, Encodable};
@@ -32,16 +33,17 @@ use crate::{
3233
};
3334

3435
#[derive(Debug, Clone)]
35-
pub struct AsyncClient {
36+
pub struct AsyncClient<S = DefaultSleeper> {
3637
/// The URL of the Esplora Server.
3738
url: String,
3839
/// The inner [`reqwest::Client`] to make HTTP requests.
3940
client: Client,
4041
/// Number of times to retry a request
4142
max_retries: usize,
43+
sleep_fn: PhantomData<S>,
4244
}
4345

44-
impl AsyncClient {
46+
impl<S: Sleeper> AsyncClient<S> {
4547
/// Build an async client from a builder
4648
pub fn from_builder(builder: Builder) -> Result<Self, Error> {
4749
let mut client_builder = Client::builder();
@@ -72,6 +74,7 @@ impl AsyncClient {
7274
url: builder.base_url,
7375
client: client_builder.build()?,
7476
max_retries: builder.max_retries,
77+
sleep_fn: PhantomData,
7578
})
7679
}
7780

@@ -81,6 +84,7 @@ impl AsyncClient {
8184
url,
8285
client,
8386
max_retries: crate::DEFAULT_MAX_RETRIES,
87+
sleep_fn: PhantomData,
8488
}
8589
}
8690

@@ -433,8 +437,7 @@ impl AsyncClient {
433437
loop {
434438
match self.client.get(url).send().await? {
435439
resp if attempts < self.max_retries && is_status_retryable(resp.status()) => {
436-
tokio::time::sleep(delay).await;
437-
// FIXME: use an sleeper_fn passed as parameter.
440+
S::sleep(delay).await;
438441
attempts += 1;
439442
delay *= 2;
440443
}
@@ -447,3 +450,21 @@ impl AsyncClient {
447450
fn is_status_retryable(status: reqwest::StatusCode) -> bool {
448451
RETRYABLE_ERROR_CODES.contains(&status.as_u16())
449452
}
453+
454+
pub trait Sleeper: 'static {
455+
type Sleep: std::future::Future<Output = ()>;
456+
457+
fn sleep(dur: std::time::Duration) -> Self::Sleep;
458+
}
459+
460+
#[derive(Default)]
461+
pub struct DefaultSleeper;
462+
463+
#[cfg(feature = "tokio")]
464+
impl Sleeper for DefaultSleeper {
465+
type Sleep = tokio::time::Sleep;
466+
467+
fn sleep(dur: std::time::Duration) -> Self::Sleep {
468+
tokio::time::sleep(dur)
469+
}
470+
}

src/lib.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,17 @@ impl Builder {
178178
BlockingClient::from_builder(self)
179179
}
180180

181-
// Build an asynchronous client from builder
182-
#[cfg(feature = "async")]
181+
/// Build an asynchronous client from builder
182+
#[cfg(all(feature = "async", feature = "tokio"))]
183183
pub fn build_async(self) -> Result<AsyncClient, Error> {
184184
AsyncClient::from_builder(self)
185185
}
186+
187+
/// Build an asynchronous client from builder
188+
#[cfg(feature = "async")]
189+
pub fn build_async_with_sleeper<S: r#async::Sleeper>(self) -> Result<AsyncClient<S>, Error> {
190+
AsyncClient::from_builder(self)
191+
}
186192
}
187193

188194
/// Errors that can happen during a request to `Esplora` servers.
@@ -253,6 +259,8 @@ mod test {
253259
use electrsd::{bitcoind, bitcoind::BitcoinD, ElectrsD};
254260
use lazy_static::lazy_static;
255261
use std::env;
262+
#[cfg(all(feature = "async", not(feature = "tokio")))]
263+
use std::{future::Future, pin::Pin};
256264
use tokio::sync::Mutex;
257265
#[cfg(all(feature = "blocking", feature = "async"))]
258266
use {
@@ -992,4 +1000,31 @@ mod test {
9921000
let tx_async = async_client.get_tx(&txid).await.unwrap();
9931001
assert_eq!(tx, tx_async);
9941002
}
1003+
1004+
#[cfg(all(feature = "async", feature = "tokio"))]
1005+
#[test]
1006+
fn test_default_tokio_sleeper() {
1007+
let builder = Builder::new("https://blockstream.info/testnet/api");
1008+
let client = builder.build_async();
1009+
assert!(client.is_ok());
1010+
}
1011+
#[cfg(all(feature = "async", not(feature = "tokio")))]
1012+
struct CustomRuntime;
1013+
1014+
#[cfg(all(feature = "async", not(feature = "tokio")))]
1015+
impl r#async::Sleeper for CustomRuntime {
1016+
type Sleep = Pin<Box<dyn Future<Output = ()>>>;
1017+
1018+
fn sleep(dur: std::time::Duration) -> Self::Sleep {
1019+
Box::pin(async_std::task::sleep(dur))
1020+
}
1021+
}
1022+
1023+
#[cfg(all(feature = "async", not(feature = "tokio")))]
1024+
#[test]
1025+
fn test_custom_runtime() {
1026+
let builder = Builder::new("https://blockstream.info/testnet/api");
1027+
let client = builder.build_async_with_sleeper::<CustomRuntime>();
1028+
assert!(client.is_ok());
1029+
}
9951030
}

0 commit comments

Comments
 (0)