Skip to content

Commit

Permalink
Remove withRetries and retrier functions, rename SimpleRepeater to Re…
Browse files Browse the repository at this point in the history
…peater (#20)
  • Loading branch information
PierreMardon authored Aug 11, 2023
1 parent 79739cb commit 71fbd1d
Show file tree
Hide file tree
Showing 15 changed files with 47 additions and 235 deletions.
75 changes: 15 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ let coldRetrier = withExponentialBackoff()
.retryOnErrors {
$0 is MyTmpError
}
// All giveUp / retry modifiers are evaluated in reversed order.
```

**All giveUp / retry modifiers are evaluated in reversed order.**

[Exponential backoff](https://aws.amazon.com/fr/blogs/architecture/exponential-backoff-and-jitter/) with
full jitter is the default and recommended algorithm to fetch from a backend.

Expand Down Expand Up @@ -98,48 +99,6 @@ the completion
- Retriers expose `successPublisher()`, `failurePublisher()` and `completionPublisher()` shortcuts.
- You can use `publisher(propagateCancellation: true)` to cancel the retrier when you're done listening to it.

## Without the main DSL

### `withRetries()` functions

You may prefer to use a function to more directly access either the `async` value of your job, or the success publisher
of your repeating job.

In this case you can use the `withRetries()` functions.

Their first argument is the `policy`. It:
- handles delays and failure criteria
- defaults to `Policy.exponentialBackoff()`
- can be built using the `Policy` entry point

```swift
let policy = Policy.exponentialBackoff().giveUpAfter(maxAttempts: 12)
let value = try await withRetries(policy: policy) { try await fetchSomething() }
// You can add an extra `attemptFailureHandler` block to log attempt errors.
// If the task executing the concurrency context is cancelled, the underlying retrier will be canceled.

withRetries(policy: Policy.exponentialBackoff(), repeatDelay: 10) { try await fetchSomething() }
.success() // If you're not interested in all events, just use .success()
.sink {
print("Got a value: \($0), let's rest 10s now")
}
// You can set `propagateCancellation` to `true` to cancel the underlying retrier when you're done listening to the
// success publisher.
```

Note that `conditionPublisher` is an optional argument to make the execution conditional.

### `retrier()` functions

Use the shortcut `retrier()` functions to build a hot retrier in one line and keep full control on it. They have
the almost same arguments as `withRetries()` and they return an executing retrier.

### Actual retrier classes

Finally, you can also use the classes initializers directly, namely `SimpleRetrier`,
`ConditionalRetrier` and `SimpleRepeater`.


## Retriers contract

- All retriers are cancellable.
Expand Down Expand Up @@ -169,42 +128,38 @@ When repeating, the policy is reused from start after each success.

### Built-in retry policies

```swift
Policy.exponentialBackoff()
Policy.constantDelay()
Policy.noDelay()
```

**Exponential backoff** policy is implemented according to state of the art algorithms.
Have a look to the available arguments and you'll recognize the standard parameters and options.
**ExponentialBackoffRetryPolicy** is implemented according to state-of-the-art algorithms.
Have a look to the available arguments, and you'll recognize the standard parameters and options.
You can especially choose the jitter type between `none`, `full` (default) and `decorrelated`.

**Constant delay** policy does what you expect, just waiting for a fixed amount of time.
**ConstantDelayRetryPolicy** does what you expect, just waiting for a fixed amount of time.

**No delay** policy is a constant delay policy with a `0` delay.

In a fallible context, you can add failure conditions using
`giveUp*()` functions, and bypass these conditions using `retry*()` functions.
You can add failure conditions using `giveUp*()` functions, and bypass these conditions using `retry*()` functions.

All giveUp / retry modifiers are evaluated in reversed order.

### Home made policy
### Homemade policy

You can create your own policies that conform `RetryPolicy` and they will benefit from the same modifiers.
Have a look at `ConstantDelayRetryPolicy.swift` for a basic example.

To create a DSL entry point using your policy:

```swift
public func withMyOwnPolicy() -> ColdInfallibleRetrier {
public func withMyOwnPolicy() -> ColdRetrier {
let policy = MyOwnPolicy()
return ColdInfallibleRetrier(policy: policy, conditionPublisher: nil)
return ColdRetrier(policy: policy, conditionPublisher: nil)
}
```

## Actual retrier classes

You can use the classes initializers directly, namely `SimpleRetrier`,
`ConditionalRetrier` and `Repeater`.

## Contribute

Feel free to make any comment, criticism, bug report or feature request using Github issues.
Feel free to make any comment, criticism, bug report or feature request using GitHub issues.
You can also directly send me an email at `pierre` *strange "a" with a long round tail* `pittscraft.com`.

## License
Expand Down
1 change: 0 additions & 1 deletion Sources/SwiftRetrier/Core/Model/Retriers/Repeater.swift

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import Combine
///
/// If the condition publisher completes and it had not emitted any value or the last value it emitted was `false`
/// then the retrier emits a completion embedding `RetryError.conditionPublisherCompleted` and finishes.
public class ConditionalRetrier<Output>: SingleOutputRetrier, SingleOutputConditionalRetrier {
public class ConditionalRetrier<Output>: SingleOutputRetrier {

private let policy: RetryPolicy
private let job: Job<Output>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Combine
/// ```
///
/// On cancellation, the publisher emits a completion embedding a `CancellationError`then finishes.
public class SimpleRepeater<Output>: Repeater, Retrier {
public class Repeater<Output>: Retrier {

private let retrierBuilder: () -> AnySingleOutputRetrier<Output>
private var retrier: AnySingleOutputRetrier<Output>?
Expand Down
6 changes: 3 additions & 3 deletions Sources/SwiftRetrier/DSL/ColdRepeater.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ public extension ColdRepeater {
}

@discardableResult
func execute<Output>(_ job: @escaping Job<Output>) -> SimpleRepeater<Output> {
SimpleRepeater(policy: policy, repeatDelay: repeatDelay, job: job)
func execute<Output>(_ job: @escaping Job<Output>) -> Repeater<Output> {
Repeater(policy: policy, repeatDelay: repeatDelay, job: job)
}

@discardableResult
func callAsFunction<Output>(_ job: @escaping Job<Output>) -> SimpleRepeater<Output> {
func callAsFunction<Output>(_ job: @escaping Job<Output>) -> Repeater<Output> {
execute(job)
}
}
23 changes: 0 additions & 23 deletions Sources/SwiftRetrier/Policy.swift

This file was deleted.

24 changes: 0 additions & 24 deletions Sources/SwiftRetrier/RetrierFunctions.swift

This file was deleted.

49 changes: 0 additions & 49 deletions Sources/SwiftRetrier/WithRetriesFunctions.swift

This file was deleted.

6 changes: 3 additions & 3 deletions Tests/SwiftRetrierTests/Common.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ func taskWait(_ time: TimeInterval = defaultWaitingTime) async throws {
try await Task.sleep(nanoseconds: nanoseconds(time))
}

extension Policy {
enum Policy {
static func testDefault(maxAttempts: UInt = UInt.max) -> RetryPolicy {
constantDelay(defaultRetryDelay).giveUpAfter(maxAttempts: maxAttempts)
ConstantDelayRetryPolicy(delay: defaultRetryDelay).giveUpAfter(maxAttempts: maxAttempts)
}

static func testDefault() -> RetryPolicy {
constantDelay(defaultRetryDelay)
ConstantDelayRetryPolicy(delay: defaultRetryDelay)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import XCTest
@testable import SwiftRetrier
import Combine

class SingleOutputConditionalRetrierTests<R: SingleOutputConditionalRetrier>: XCTestCase {
class SingleOutputConditionalRetrierTests<R: SingleOutputRetrier>: XCTestCase {

var retrier: ((AnyPublisher<Bool, Never>, Job<Void>) -> R)!

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class SingleOutputFallibleRetrierTests<R: SingleOutputRetrier>: XCTestCase {

@MainActor
func test_async_value_throws_on_trial_failure() async {
let retrier = buildRetrier(Policy.constantDelay().giveUpAfter(maxAttempts: 1), immediateFailureJob)
let retrier = buildRetrier(Policy.testDefault().giveUpAfter(maxAttempts: 1), immediateFailureJob)
do {
_ = try await retrier.value
XCTFail("Unexpected success")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,29 @@ import Foundation
import XCTest
@testable import SwiftRetrier

class RepeaterTests<R: Repeater>: XCTestCase {
var retrier: ((TimeInterval, @escaping Job<Void>) -> R)!
// swiftlint:disable type_name
class Repeater_RetrierTests: RetrierTests<Repeater<Void>> {
override func setUp() {
self.retrier = {
Repeater(policy: Policy.testDefault(), repeatDelay: 100, job: $0)
}
}
}

class Repeater_FallibleRetrierTests: FallibleRetrierTests<Repeater<Void>> {
override func setUp() {
self.retrier = {
Repeater(policy: $0, repeatDelay: 100, job: $1)
}
}
}

class RepeaterTests: XCTestCase {
var retrier: ((TimeInterval, @escaping Job<Void>) -> Repeater<Void>)!

private var instance: R?
private var instance: Repeater<Void>?

func buildRetrier(_ repeatDelay: TimeInterval, _ job: @escaping Job<Void>) -> R {
func buildRetrier(_ repeatDelay: TimeInterval, _ job: @escaping Job<Void>) -> Repeater<Void> {
let retrier = retrier(repeatDelay, job)
instance = retrier
return retrier
Expand Down Expand Up @@ -39,3 +56,4 @@ class RepeaterTests<R: Repeater>: XCTestCase {
}
}
}
// swiftlint:enable type_name
29 changes: 0 additions & 29 deletions Tests/SwiftRetrierTests/Retriers/SimpleRepeaterTests.swift

This file was deleted.

34 changes: 0 additions & 34 deletions Tests/SwiftRetrierTests/Retriers/WithRetriesFunctionsTests.swift

This file was deleted.

0 comments on commit 71fbd1d

Please sign in to comment.