summaryrefslogtreecommitdiffhomepage
path: root/mullvad-ios/src/api_client
diff options
context:
space:
mode:
authorEmīls <emils@mullvad.net>2025-02-19 15:55:20 +0100
committerEmīls <emils@mullvad.net>2025-03-03 14:23:34 +0100
commit68352d1e0e5e5e285d8018cd382e128c136bd194 (patch)
tree5423c1aa5d1b01dcadd2aa6f29ddcb2dfadf2376 /mullvad-ios/src/api_client
parentbf43d23c3f2317ee3cba2702e57a608c72f835fa (diff)
downloadmullvadvpn-68352d1e0e5e5e285d8018cd382e128c136bd194.tar.xz
mullvadvpn-68352d1e0e5e5e285d8018cd382e128c136bd194.zip
Add retry strategy to mullvad api
Diffstat (limited to 'mullvad-ios/src/api_client')
-rw-r--r--mullvad-ios/src/api_client/api.rs17
-rw-r--r--mullvad-ios/src/api_client/mod.rs1
-rw-r--r--mullvad-ios/src/api_client/response.rs11
-rw-r--r--mullvad-ios/src/api_client/retry_strategy.rs99
4 files changed, 115 insertions, 13 deletions
diff --git a/mullvad-ios/src/api_client/api.rs b/mullvad-ios/src/api_client/api.rs
index ad3069a20b..847e81f0eb 100644
--- a/mullvad-ios/src/api_client/api.rs
+++ b/mullvad-ios/src/api_client/api.rs
@@ -2,11 +2,13 @@ use mullvad_api::{
rest::{self, MullvadRestHandle},
ApiProxy,
};
+use talpid_future::retry::retry_future;
use super::{
cancellation::{RequestCancelHandle, SwiftCancelHandle},
completion::{CompletionCookie, SwiftCompletionHandler},
response::SwiftMullvadApiResponse,
+ retry_strategy::{RetryStrategy, SwiftRetryStrategy},
SwiftApiContext,
};
@@ -24,6 +26,7 @@ use super::{
pub unsafe extern "C" fn mullvad_api_get_addresses(
api_context: SwiftApiContext,
completion_cookie: *mut libc::c_void,
+ retry_strategy: SwiftRetryStrategy,
) -> SwiftCancelHandle {
let completion_handler = SwiftCompletionHandler::new(CompletionCookie(completion_cookie));
@@ -33,10 +36,11 @@ pub unsafe extern "C" fn mullvad_api_get_addresses(
};
let api_context = api_context.into_rust_context();
+ let retry_strategy = unsafe { retry_strategy.into_rust() };
let completion = completion_handler.clone();
let task = tokio_handle.clone().spawn(async move {
- match mullvad_api_get_addresses_inner(api_context.rest_handle()).await {
+ match mullvad_api_get_addresses_inner(api_context.rest_handle(), retry_strategy).await {
Ok(response) => completion.finish(response),
Err(err) => {
log::error!("{err:?}");
@@ -50,9 +54,18 @@ pub unsafe extern "C" fn mullvad_api_get_addresses(
async fn mullvad_api_get_addresses_inner(
rest_client: MullvadRestHandle,
+ retry_strategy: RetryStrategy,
) -> Result<SwiftMullvadApiResponse, rest::Error> {
let api = ApiProxy::new(rest_client);
- let response = api.get_api_addrs_response().await?;
+
+ let future_factory = || api.get_api_addrs_response();
+
+ let should_retry = |result: &Result<_, rest::Error>| match result {
+ Err(err) => err.is_network_error(),
+ Ok(_) => false,
+ };
+
+ let response = retry_future(future_factory, should_retry, retry_strategy.delays()).await?;
SwiftMullvadApiResponse::with_body(response).await
}
diff --git a/mullvad-ios/src/api_client/mod.rs b/mullvad-ios/src/api_client/mod.rs
index fdda5b0dbb..98443fd0d9 100644
--- a/mullvad-ios/src/api_client/mod.rs
+++ b/mullvad-ios/src/api_client/mod.rs
@@ -10,6 +10,7 @@ mod api;
mod cancellation;
mod completion;
mod response;
+mod retry_strategy;
#[repr(C)]
pub struct SwiftApiContext(*const ApiContext);
diff --git a/mullvad-ios/src/api_client/response.rs b/mullvad-ios/src/api_client/response.rs
index 249f1040bd..6ffbadb5d6 100644
--- a/mullvad-ios/src/api_client/response.rs
+++ b/mullvad-ios/src/api_client/response.rs
@@ -10,8 +10,6 @@ pub struct SwiftMullvadApiResponse {
error_description: *mut u8,
server_response_code: *mut u8,
success: bool,
- should_retry: bool,
- retry_after: u64,
}
impl SwiftMullvadApiResponse {
pub async fn with_body(response: Response<hyper::body::Incoming>) -> Result<Self, rest::Error> {
@@ -28,8 +26,6 @@ impl SwiftMullvadApiResponse {
error_description: null_mut(),
server_response_code: null_mut(),
success: true,
- should_retry: false,
- retry_after: 0,
})
}
@@ -44,7 +40,6 @@ impl SwiftMullvadApiResponse {
.unwrap_or(null_mut())
};
- let should_retry = err.is_network_error();
let error_description = to_cstr_pointer(err.to_string());
let (status_code, server_response_code): (u16, _) =
if let rest::Error::ApiError(status_code, error_code) = err {
@@ -60,34 +55,28 @@ impl SwiftMullvadApiResponse {
error_description,
server_response_code,
success: false,
- should_retry,
- retry_after: 0,
}
}
pub fn cancelled() -> Self {
Self {
success: false,
- should_retry: false,
error_description: c"Request was cancelled".to_owned().into_raw().cast(),
body: null_mut(),
body_size: 0,
status_code: 0,
server_response_code: null_mut(),
- retry_after: 0,
}
}
pub fn no_tokio_runtime() -> Self {
Self {
success: false,
- should_retry: false,
error_description: c"Failed to get Tokio runtime".to_owned().into_raw().cast(),
body: null_mut(),
body_size: 0,
status_code: 0,
server_response_code: null_mut(),
- retry_after: 0,
}
}
}
diff --git a/mullvad-ios/src/api_client/retry_strategy.rs b/mullvad-ios/src/api_client/retry_strategy.rs
new file mode 100644
index 0000000000..80a3b9668c
--- /dev/null
+++ b/mullvad-ios/src/api_client/retry_strategy.rs
@@ -0,0 +1,99 @@
+use std::time::Duration;
+
+use talpid_future::retry::{ConstantInterval, ExponentialBackoff, Jittered};
+
+#[repr(C)]
+pub struct SwiftRetryStrategy(*mut RetryStrategy);
+
+impl SwiftRetryStrategy {
+ /// # Safety
+ /// The pointer must be a pointing to a valid instance of a `Box<RetryStrategy>`.
+ pub unsafe fn into_rust(self) -> RetryStrategy {
+ *Box::from_raw(self.0)
+ }
+}
+
+pub struct RetryStrategy {
+ delays: RetryDelay,
+ max_retries: usize,
+}
+
+impl RetryStrategy {
+ pub fn delays(self) -> impl Iterator<Item = Duration> + Send {
+ let Self {
+ delays,
+ max_retries,
+ } = self;
+
+ let delays: Box<dyn Iterator<Item = Duration> + Send> = match delays {
+ RetryDelay::Never => Box::new(std::iter::empty()),
+ RetryDelay::Constant(constant_delays) => Box::new(constant_delays.take(max_retries)),
+ RetryDelay::Exponential(exponential_delays) => {
+ Box::new(exponential_delays.take(max_retries))
+ }
+ };
+
+ Jittered::jitter(delays)
+ }
+}
+
+#[repr(C)]
+pub enum RetryDelay {
+ Never,
+ Constant(ConstantInterval),
+ Exponential(ExponentialBackoff),
+}
+
+/// Creates a retry strategy that never retries after failure.
+/// The result needs to be consumed.
+#[no_mangle]
+pub extern "C" fn mullvad_api_retry_strategy_never() -> SwiftRetryStrategy {
+ let retry_strategy = RetryStrategy {
+ delays: RetryDelay::Never,
+ max_retries: 0,
+ };
+
+ let ptr = Box::into_raw(Box::new(retry_strategy));
+ SwiftRetryStrategy(ptr)
+}
+
+/// Creates a retry strategy that retries `max_retries` times with a constant delay of `delay_sec`.
+/// The result needs to be consumed.
+#[no_mangle]
+pub extern "C" fn mullvad_api_retry_strategy_constant(
+ max_retries: usize,
+ delay_sec: u64,
+) -> SwiftRetryStrategy {
+ let interval = Duration::from_secs(delay_sec);
+ let retry_strategy = RetryStrategy {
+ delays: RetryDelay::Constant(ConstantInterval::new(interval, Some(max_retries))),
+ max_retries: 0,
+ };
+ let ptr = Box::into_raw(Box::new(retry_strategy));
+
+ SwiftRetryStrategy(ptr)
+}
+
+/// Creates a retry strategy that retries `max_retries` times with a exponantially increating delay.
+/// The delay will never exceed `max_delay_sec`
+/// The result needs to be consumed.
+#[no_mangle]
+pub extern "C" fn mullvad_api_retry_strategy_exponential(
+ max_retries: usize,
+ initial_sec: u64,
+ factor: u32,
+ max_delay_sec: u64,
+) -> SwiftRetryStrategy {
+ let initial_delay = Duration::from_secs(initial_sec);
+
+ let backoff = ExponentialBackoff::new(initial_delay, factor)
+ .max_delay(Some(Duration::from_secs(max_delay_sec)));
+
+ let retry_strategy = RetryStrategy {
+ delays: RetryDelay::Exponential(backoff),
+ max_retries,
+ };
+
+ let ptr = Box::into_raw(Box::new(retry_strategy));
+ SwiftRetryStrategy(ptr)
+}