Skip to content

Commit

Permalink
feat: allow sending with retries
Browse files Browse the repository at this point in the history
  • Loading branch information
ctron committed Jan 19, 2024
1 parent 59c9fdb commit baa66de
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 6 deletions.
10 changes: 10 additions & 0 deletions csaf/csaf-cli/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ pub struct SendArguments {
#[arg(id = "sender-timeout", long, default_value = "5m")]
pub timeout: humantime::Duration,

/// Number of retries in case of temporary failures
#[arg(id = "retries", long, default_value = "0")]
pub retries: usize,

/// Delay between retries
#[arg(id = "retry-delay", long, default_value = "5s")]
pub retry_delay: humantime::Duration,

#[command(flatten)]
pub oidc: OpenIdTokenProviderConfigArguments,
}
Expand All @@ -120,6 +128,8 @@ impl SendArguments {
Ok(SendVisitor {
url: self.target,
sender,
retries: self.retries,
retry_delay: Some(self.retry_delay.into()),
})
}
}
Expand Down
1 change: 1 addition & 0 deletions extras/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ async-trait = "0.1"
bytes = "1"
log = "0.4"
thiserror = "1"
tokio = { version = "1", features = ["time"] }
reqwest = "0.11"

walker-common = { version = "0.6.0-alpha.2", path = "../common" }
Expand Down
63 changes: 57 additions & 6 deletions extras/src/visitors/send/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use async_trait::async_trait;
use bytes::Bytes;
use reqwest::{Body, Method, StatusCode, Url};
use std::time::Duration;
use walker_common::sender::{self, HttpSender};

#[cfg(feature = "sbom-walker")]
Expand Down Expand Up @@ -34,20 +35,44 @@ pub struct SendVisitor {

/// The HTTP client to use
pub sender: HttpSender,

/// The number of retries in case of a server or transmission failure
pub retries: usize,

/// The delay between retries
pub retry_delay: Option<Duration>,
}

/// The default amount of time to wait before trying
const DEFAULT_RETRY_DELAY: Duration = Duration::from_secs(5);

pub enum SendOnceError {
Temporary(SendError),
Permanent(SendError),
}

impl SendVisitor {
async fn send<F>(&self, name: &str, data: Bytes, customizer: F) -> Result<(), SendError>
/// Send request once
async fn send_once<F>(
&self,
name: &str,
data: Bytes,
customizer: F,
) -> Result<(), SendOnceError>
where
F: FnOnce(reqwest::RequestBuilder) -> reqwest::RequestBuilder,
{
let request = self
.sender
.request(Method::POST, self.url.clone())
.await?
.await
.map_err(|err| SendOnceError::Temporary(err.into()))?
.body(Body::from(data));
let request = customizer(request);
let response = request.send().await?;
let response = request
.send()
.await
.map_err(|err| SendOnceError::Temporary(err.into()))?;

let status = response.status();

Expand All @@ -56,12 +81,38 @@ impl SendVisitor {
Ok(())
} else if status.is_client_error() {
log::warn!("Failed to upload, payload rejected {name} -> {status}",);
Err(SendError::Client(status))
Err(SendOnceError::Permanent(SendError::Client(status)))
} else if status.is_server_error() {
log::warn!("Failed to upload, server error {name} -> {status}",);
Err(SendError::Server(status))
Err(SendOnceError::Temporary(SendError::Server(status)))
} else {
Err(SendError::UnexpectedStatus(status))
Err(SendOnceError::Permanent(SendError::UnexpectedStatus(
status,
)))
}
}

/// Send request, retry in case of temporary errors
async fn send<F>(&self, name: &str, data: Bytes, customizer: F) -> Result<(), SendError>
where
F: Fn(reqwest::RequestBuilder) -> reqwest::RequestBuilder,
{
let mut retries = self.retries;
loop {
match self.send_once(name, data.clone(), &customizer).await {
Ok(()) => break Ok(()),
Err(SendOnceError::Permanent(err)) => break Err(err),
Err(SendOnceError::Temporary(err)) if retries == 0 => break Err(err),
Err(SendOnceError::Temporary(_)) => {
log::debug!("Failed with a temporary error, retrying ...");
}
}

// sleep, then try again

tokio::time::sleep(self.retry_delay.unwrap_or(DEFAULT_RETRY_DELAY)).await;
log::info!("Retrying ({retries} attempts left)");
retries -= 1;
}
}
}
10 changes: 10 additions & 0 deletions sbom/sbom-cli/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ pub struct SendArguments {
#[arg(id = "sender-timeout", long, default_value = "5m")]
pub timeout: humantime::Duration,

/// Number of retries in case of temporary failures
#[arg(id = "retries", long, default_value = "0")]
pub retries: usize,

/// Delay between retries
#[arg(id = "retry-delay", long, default_value = "5s")]
pub retry_delay: humantime::Duration,

#[command(flatten)]
pub oidc: OpenIdTokenProviderConfigArguments,
}
Expand All @@ -87,6 +95,8 @@ impl SendArguments {
Ok(SendVisitor {
url: self.target,
sender,
retries: self.retries,
retry_delay: Some(self.retry_delay.into()),
})
}
}
Expand Down

0 comments on commit baa66de

Please sign in to comment.