Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Change Log
All notable changes to this project will be documented in this file.

## [Unreleased]

#### Added
- Mark the closures as @Sendable.
- Added in Pull Request [#18](https://github.com/space-code/typhoon/pull/18)

#### 1.x Releases
- `1.2.x` Releases - [1.2.0](#120)
- `1.1.x` Releases - [1.1.0](#110) | [1.1.1](#111)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
//
// Typhoon
// Copyright © 2023 Space Code. All rights reserved.
// Copyright © 2024 Space Code. All rights reserved.
//

import Foundation

// MARK: - IRetryPolicyService

/// A type that defines a service for retry policies
public protocol IRetryPolicyService {
public protocol IRetryPolicyService: Sendable {
/// Retries a closure with a given strategy.
///
/// - Parameters:
Expand All @@ -19,8 +19,8 @@ public protocol IRetryPolicyService {
/// - Returns: The result of the closure's execution after retrying based on the policy.
func retry<T>(
strategy: RetryPolicyStrategy?,
onFailure: ((Error) async -> Void)?,
_ closure: () async throws -> T
onFailure: (@Sendable (Error) async -> Void)?,
_ closure: @Sendable () async throws -> T
) async throws -> T
}

Expand All @@ -31,7 +31,7 @@ public extension IRetryPolicyService {
/// - closure: The closure that will be retried based on the specified strategy.
///
/// - Returns: The result of the closure's execution after retrying based on the policy.
func retry<T>(_ closure: () async throws -> T) async throws -> T {
func retry<T>(_ closure: @Sendable () async throws -> T) async throws -> T {
try await retry(strategy: nil, onFailure: nil, closure)
}

Expand All @@ -42,7 +42,7 @@ public extension IRetryPolicyService {
/// - closure: The closure that will be retried based on the specified strategy.
///
/// - Returns: The result of the closure's execution after retrying based on the policy.
func retry<T>(strategy: RetryPolicyStrategy?, _ closure: () async throws -> T) async throws -> T {
func retry<T>(strategy: RetryPolicyStrategy?, _ closure: @Sendable () async throws -> T) async throws -> T {
try await retry(strategy: strategy, onFailure: nil, closure)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// Typhoon
// Copyright © 2023 Space Code. All rights reserved.
// Copyright © 2024 Space Code. All rights reserved.
//

import Foundation
Expand Down Expand Up @@ -29,8 +29,8 @@ public final class RetryPolicyService {
extension RetryPolicyService: IRetryPolicyService {
public func retry<T>(
strategy: RetryPolicyStrategy?,
onFailure: ((Error) async -> Void)?,
_ closure: () async throws -> T
onFailure: (@Sendable (Error) async -> Void)?,
_ closure: @Sendable () async throws -> T
) async throws -> T {
for duration in RetrySequence(strategy: strategy ?? self.strategy) {
try Task.checkCancellation()
Expand Down
4 changes: 2 additions & 2 deletions Sources/Typhoon/Classes/Strategy/RetryPolicyStrategy.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//
// Typhoon
// Copyright © 2023 Space Code. All rights reserved.
// Copyright © 2024 Space Code. All rights reserved.
//

import Foundation

/// A strategy used to define different retry policies.
public enum RetryPolicyStrategy {
public enum RetryPolicyStrategy: Sendable {
/// A retry strategy with a constant number of attempts and fixed duration between retries.
///
/// - Parameters:
Expand Down
49 changes: 41 additions & 8 deletions Tests/TyphoonTests/UnitTests/RetryPolicyServiceTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// Typhoon
// Copyright © 2023 Space Code. All rights reserved.
// Copyright © 2024 Space Code. All rights reserved.
//

import Typhoon
Expand Down Expand Up @@ -42,39 +42,72 @@ final class RetryPolicyServiceTests: XCTestCase {

func test_thatRetryServiceDoesNotThrowAnError_whenServiceDidReturnValue() async throws {
// given
var counter = 0
actor Counter {
// MARK: Properties

private var value: Int = 0

// MARK: Internal

func increment() -> Int {
value += 1
return value
}
}

let counter = Counter()

// when
_ = try await sut.retry(
strategy: .constant(retry: .retry, duration: .nanoseconds(1)),
{
counter += 1
let currentCounter = await counter.increment()

if counter > .retry - 1 {
if currentCounter > .retry - 1 {
return 1
}
throw URLError(.unknown)
}
)

// then
XCTAssertEqual(counter, .retry)
let finalValue = await counter.increment() - 1
XCTAssertEqual(finalValue, .retry)
}

func test_thatRetryServiceHandlesErrorOnFailureCallback_whenErrorOcurred() async {
// given
actor ErrorContainer {
// MARK: Private

private var error: NSError?

// MARK: Internal

func setError(_ newError: NSError) {
error = newError
}

func getError() -> NSError? {
error
}
}

let errorContainer = ErrorContainer()

// when
var failureError: NSError?
do {
_ = try await sut.retry(
strategy: .constant(retry: .retry, duration: .nanoseconds(1)),
onFailure: { error in failureError = error as NSError }
onFailure: { error in await errorContainer.setError(error as NSError) }
) {
throw URLError(.unknown)
}
} catch {}

// then
XCTAssertEqual(failureError as? URLError, URLError(.unknown))
let capturedError = await errorContainer.getError()
XCTAssertEqual(capturedError as? URLError, URLError(.unknown))
}
}

Expand Down
Loading