summaryrefslogtreecommitdiffhomepage
path: root/ios
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 /ios
parentbf43d23c3f2317ee3cba2702e57a608c72f835fa (diff)
downloadmullvadvpn-68352d1e0e5e5e285d8018cd382e128c136bd194.tar.xz
mullvadvpn-68352d1e0e5e5e285d8018cd382e128c136bd194.zip
Add retry strategy to mullvad api
Diffstat (limited to 'ios')
-rw-r--r--ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift10
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift3
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTRustNetworkOperation.swift63
-rw-r--r--ios/MullvadREST/RetryStrategy/RetryStrategy.swift18
-rw-r--r--ios/MullvadRustRuntime/MullvadApiResponse.swift4
-rw-r--r--ios/MullvadRustRuntime/include/mullvad_rust_runtime.h34
-rw-r--r--ios/MullvadTypes/Duration+Extensions.swift4
7 files changed, 62 insertions, 74 deletions
diff --git a/ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift b/ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift
index 89bf2dd725..d40039d558 100644
--- a/ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift
+++ b/ios/MullvadREST/ApiHandlers/MullvadApiRequestFactory.swift
@@ -10,7 +10,7 @@ import MullvadRustRuntime
import MullvadTypes
enum MullvadApiRequest {
- case getAddressList
+ case getAddressList(retryStrategy: REST.RetryStrategy)
}
struct MullvadApiRequestFactory {
@@ -25,8 +25,12 @@ struct MullvadApiRequestFactory {
let rawPointer = Unmanaged.passRetained(pointerClass).toOpaque()
return switch request {
- case .getAddressList:
- MullvadApiCancellable(handle: mullvad_api_get_addresses(apiContext.context, rawPointer))
+ case let .getAddressList(retryStrategy):
+ MullvadApiCancellable(handle: mullvad_api_get_addresses(
+ apiContext.context,
+ rawPointer,
+ retryStrategy.toRustStrategy()
+ ))
}
}
}
diff --git a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
index 4c8e144d6d..1dfd01b545 100644
--- a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift
@@ -66,7 +66,7 @@ extension REST {
retryStrategy: REST.RetryStrategy,
completionHandler: @escaping @Sendable ProxyCompletionHandler<[AnyIPEndpoint]>
) -> Cancellable {
- let requestHandler = mullvadApiRequestFactory.makeRequest(.getAddressList)
+ let requestHandler = mullvadApiRequestFactory.makeRequest(.getAddressList(retryStrategy: retryStrategy))
let responseHandler = rustResponseHandler(
decoding: [AnyIPEndpoint].self,
@@ -76,7 +76,6 @@ extension REST {
let networkOperation = MullvadApiNetworkOperation(
name: "get-api-addrs",
dispatchQueue: dispatchQueue,
- retryStrategy: retryStrategy,
requestHandler: requestHandler,
responseDecoder: responseDecoder,
responseHandler: responseHandler,
diff --git a/ios/MullvadREST/ApiHandlers/RESTRustNetworkOperation.swift b/ios/MullvadREST/ApiHandlers/RESTRustNetworkOperation.swift
index 1bcd218444..378da03425 100644
--- a/ios/MullvadREST/ApiHandlers/RESTRustNetworkOperation.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTRustNetworkOperation.swift
@@ -21,22 +21,14 @@ extension REST {
private let responseHandler: any RESTRustResponseHandler<Success>
private var networkTask: MullvadApiCancellable?
- private let retryStrategy: RetryStrategy
- private var retryDelayIterator: AnyIterator<Duration>
- private var retryTimer: DispatchSourceTimer?
- private var retryCount = 0
-
init(
name: String,
dispatchQueue: DispatchQueue,
- retryStrategy: RetryStrategy,
requestHandler: @escaping MullvadApiRequestHandler,
responseDecoder: JSONDecoder,
responseHandler: some RESTRustResponseHandler<Success>,
completionHandler: CompletionHandler? = nil
) {
- self.retryStrategy = retryStrategy
- retryDelayIterator = retryStrategy.makeDelayIterator()
self.responseDecoder = responseDecoder
self.requestHandler = requestHandler
self.responseHandler = responseHandler
@@ -53,10 +45,7 @@ extension REST {
}
override public func operationDidCancel() {
- retryTimer?.cancel()
networkTask?.cancel()
-
- retryTimer = nil
networkTask = nil
}
@@ -76,12 +65,7 @@ extension REST {
guard let self else { return }
if let error = response.restError() {
- if response.shouldRetry {
- retryRequest(with: error)
- } else {
- finish(result: .failure(error))
- }
-
+ finish(result: .failure(error))
return
}
@@ -97,51 +81,6 @@ extension REST {
}
}
}
-
- private func retryRequest(with error: REST.Error) {
- // Check if retry count is not exceeded.
- guard retryCount < retryStrategy.maxRetryCount else {
- if retryStrategy.maxRetryCount > 0 {
- logger.debug("Ran out of retry attempts (\(retryStrategy.maxRetryCount))")
- }
- finish(result: .failure(error))
- return
- }
-
- // Increment retry count.
- retryCount += 1
-
- // Retry immediately if retry delay is set to never.
- guard retryStrategy.delay != .never else {
- startRequest()
- return
- }
-
- guard let waitDelay = retryDelayIterator.next() else {
- logger.debug("Retry delay iterator failed to produce next value.")
-
- finish(result: .failure(error))
- return
- }
-
- logger.debug("Retry in \(waitDelay.logFormat()).")
-
- // Create timer to delay retry.
- let timer = DispatchSource.makeTimerSource(queue: dispatchQueue)
-
- timer.setEventHandler { [weak self] in
- self?.startRequest()
- }
-
- timer.setCancelHandler { [weak self] in
- self?.finish(result: .failure(OperationError.cancelled))
- }
-
- timer.schedule(wallDeadline: .now() + waitDelay.timeInterval)
- timer.activate()
-
- retryTimer = timer
- }
}
}
diff --git a/ios/MullvadREST/RetryStrategy/RetryStrategy.swift b/ios/MullvadREST/RetryStrategy/RetryStrategy.swift
index 3e996a9205..4f0d7016af 100644
--- a/ios/MullvadREST/RetryStrategy/RetryStrategy.swift
+++ b/ios/MullvadREST/RetryStrategy/RetryStrategy.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadRustRuntime
import MullvadTypes
extension REST {
@@ -21,6 +22,23 @@ extension REST {
self.applyJitter = applyJitter
}
+ /// The return value of this function *must* be passed to a Rust FFI function that will consume it, otherwise it will leak.
+ public func toRustStrategy() -> SwiftRetryStrategy {
+ switch delay {
+ case .never:
+ return mullvad_api_retry_strategy_never()
+ case let .constant(duration):
+ return mullvad_api_retry_strategy_constant(UInt(maxRetryCount), UInt64(duration.seconds))
+ case let .exponentialBackoff(initial, multiplier, maxDelay):
+ return mullvad_api_retry_strategy_exponential(
+ UInt(maxRetryCount),
+ UInt64(initial.seconds),
+ UInt32(multiplier),
+ UInt64(maxDelay.seconds)
+ )
+ }
+ }
+
public func makeDelayIterator() -> AnyIterator<Duration> {
let inner = delay.makeIterator()
diff --git a/ios/MullvadRustRuntime/MullvadApiResponse.swift b/ios/MullvadRustRuntime/MullvadApiResponse.swift
index a709342b4e..7836d43971 100644
--- a/ios/MullvadRustRuntime/MullvadApiResponse.swift
+++ b/ios/MullvadRustRuntime/MullvadApiResponse.swift
@@ -45,10 +45,6 @@ public class MullvadApiResponse {
}
}
- public var shouldRetry: Bool {
- response.should_retry
- }
-
public var success: Bool {
response.success
}
diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
index faae315d6d..e054de0044 100644
--- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
+++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
@@ -26,6 +26,8 @@ typedef struct ExchangeCancelToken ExchangeCancelToken;
typedef struct RequestCancelHandle RequestCancelHandle;
+typedef struct RetryStrategy RetryStrategy;
+
typedef struct SwiftApiContext {
const struct ApiContext *_0;
} SwiftApiContext;
@@ -34,6 +36,10 @@ typedef struct SwiftCancelHandle {
struct RequestCancelHandle *ptr;
} SwiftCancelHandle;
+typedef struct SwiftRetryStrategy {
+ struct RetryStrategy *_0;
+} SwiftRetryStrategy;
+
typedef struct SwiftMullvadApiResponse {
uint8_t *body;
uintptr_t body_size;
@@ -41,8 +47,6 @@ typedef struct SwiftMullvadApiResponse {
uint8_t *error_description;
uint8_t *server_response_code;
bool success;
- bool should_retry;
- uint64_t retry_after;
} SwiftMullvadApiResponse;
typedef struct CompletionCookie {
@@ -106,7 +110,8 @@ struct SwiftApiContext mullvad_api_init_new(const uint8_t *host,
* This function is not safe to call multiple times with the same `CompletionCookie`.
*/
struct SwiftCancelHandle mullvad_api_get_addresses(struct SwiftApiContext api_context,
- void *completion_cookie);
+ void *completion_cookie,
+ struct SwiftRetryStrategy retry_strategy);
/**
* Called by the Swift side to signal that a Mullvad API call should be cancelled.
@@ -157,6 +162,29 @@ extern void mullvad_api_completion_finish(struct SwiftMullvadApiResponse respons
void mullvad_response_drop(struct SwiftMullvadApiResponse response);
/**
+ * Creates a retry strategy that never retries after failure.
+ * The result needs to be consumed.
+ */
+struct SwiftRetryStrategy mullvad_api_retry_strategy_never(void);
+
+/**
+ * Creates a retry strategy that retries `max_retries` times with a constant delay of `delay_sec`.
+ * The result needs to be consumed.
+ */
+struct SwiftRetryStrategy mullvad_api_retry_strategy_constant(uintptr_t max_retries,
+ uint64_t delay_sec);
+
+/**
+ * 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.
+ */
+struct SwiftRetryStrategy mullvad_api_retry_strategy_exponential(uintptr_t max_retries,
+ uint64_t initial_sec,
+ uint32_t factor,
+ uint64_t max_delay_sec);
+
+/**
* Initializes a valid pointer to an instance of `EncryptedDnsProxyState`.
*
* # Safety
diff --git a/ios/MullvadTypes/Duration+Extensions.swift b/ios/MullvadTypes/Duration+Extensions.swift
index e4a63a6b4a..e01fa4f883 100644
--- a/ios/MullvadTypes/Duration+Extensions.swift
+++ b/ios/MullvadTypes/Duration+Extensions.swift
@@ -14,6 +14,10 @@ extension Duration {
return timeInterval.isFinite
}
+ public var seconds: Int64 {
+ return components.seconds
+ }
+
public var timeInterval: TimeInterval {
return TimeInterval(components.seconds) + (TimeInterval(components.attoseconds) * 1e-18)
}