Skip to content

Commit a05138d

Browse files
committed
Retry faucet operations on benchmark
1 parent 6b998e9 commit a05138d

File tree

1 file changed

+54
-4
lines changed

1 file changed

+54
-4
lines changed

linera-service/src/cli/main.rs

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,48 @@ use tracing::{debug, error, info, warn, Instrument as _};
9898

9999
struct Job(ClientOptions);
100100

101+
/// Check if an error is retryable (HTTP 502, 503, 504, timeouts, connection errors)
102+
fn is_retryable_error(err: &anyhow::Error) -> bool {
103+
// Check for reqwest errors in the error chain
104+
if let Some(reqwest_err) = err.downcast_ref::<reqwest::Error>() {
105+
// Check for retryable HTTP status codes (502, 503, 504)
106+
if let Some(status) = reqwest_err.status() {
107+
return status == reqwest::StatusCode::BAD_GATEWAY
108+
|| status == reqwest::StatusCode::SERVICE_UNAVAILABLE
109+
|| status == reqwest::StatusCode::GATEWAY_TIMEOUT;
110+
}
111+
// Check for connection errors or timeouts
112+
return reqwest_err.is_timeout() || reqwest_err.is_connect();
113+
}
114+
false
115+
}
116+
117+
/// Retry a faucet operation with exponential backoff
118+
async fn retry_faucet_operation<F, Fut, T>(operation: F) -> anyhow::Result<T>
119+
where
120+
F: Fn() -> Fut,
121+
Fut: std::future::Future<Output = anyhow::Result<T>>,
122+
{
123+
let max_retries = 5;
124+
let mut attempt = 0;
125+
126+
loop {
127+
attempt += 1;
128+
match operation().await {
129+
Ok(result) => return Ok(result),
130+
Err(err) if attempt < max_retries && is_retryable_error(&err) => {
131+
let backoff_ms = 100 * 2_u64.pow(attempt - 1);
132+
warn!(
133+
"Faucet operation failed with retryable error (attempt {}/{}): {:?}. Retrying after {}ms",
134+
attempt, max_retries, err, backoff_ms
135+
);
136+
tokio::time::sleep(Duration::from_millis(backoff_ms)).await;
137+
}
138+
Err(err) => return Err(err),
139+
}
140+
}
141+
}
142+
101143
fn read_json(string: Option<String>, path: Option<PathBuf>) -> anyhow::Result<Vec<u8>> {
102144
let value = match (string, path) {
103145
(Some(_), Some(_)) => bail!("cannot have both a json string and file"),
@@ -1108,8 +1150,12 @@ impl Runnable for Job {
11081150
let client = client.clone();
11091151
let faucet_client = faucet_client.clone();
11101152
join_set.spawn(async move {
1111-
client.wallet_init(Some(&faucet_client)).await?;
1112-
client.request_chain(&faucet_client, true).await?;
1153+
retry_faucet_operation(|| client.wallet_init(Some(&faucet_client)))
1154+
.await?;
1155+
retry_faucet_operation(|| {
1156+
client.request_chain(&faucet_client, true)
1157+
})
1158+
.await?;
11131159
Ok::<_, anyhow::Error>(())
11141160
});
11151161
}
@@ -1172,8 +1218,12 @@ impl Runnable for Job {
11721218
for client in clients.clone() {
11731219
let faucet_client = faucet_client.clone();
11741220
join_set.spawn(async move {
1175-
client.wallet_init(Some(&faucet_client)).await?;
1176-
client.request_chain(&faucet_client, true).await?;
1221+
retry_faucet_operation(|| client.wallet_init(Some(&faucet_client)))
1222+
.await?;
1223+
retry_faucet_operation(|| {
1224+
client.request_chain(&faucet_client, true)
1225+
})
1226+
.await?;
11771227
Ok::<_, anyhow::Error>(())
11781228
});
11791229
}

0 commit comments

Comments
 (0)