diff options
| author | Emīls <emils@mullvad.net> | 2025-02-19 15:55:20 +0100 |
|---|---|---|
| committer | Emīls <emils@mullvad.net> | 2025-03-03 14:23:34 +0100 |
| commit | 68352d1e0e5e5e285d8018cd382e128c136bd194 (patch) | |
| tree | 5423c1aa5d1b01dcadd2aa6f29ddcb2dfadf2376 /mullvad-ios/src/api_client | |
| parent | bf43d23c3f2317ee3cba2702e57a608c72f835fa (diff) | |
| download | mullvadvpn-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.rs | 17 | ||||
| -rw-r--r-- | mullvad-ios/src/api_client/mod.rs | 1 | ||||
| -rw-r--r-- | mullvad-ios/src/api_client/response.rs | 11 | ||||
| -rw-r--r-- | mullvad-ios/src/api_client/retry_strategy.rs | 99 |
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) +} |
