blob: 1e73e81d792104ba62c5ce32c35eca6c0c260084 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
//
// RetryOperation.swift
// MullvadVPN
//
// Created by pronebird on 06/07/2020.
// Copyright © 2020 Mullvad VPN AB. All rights reserved.
//
import Foundation
enum WaitStrategy {
case immediate
case constant(TimeInterval)
var iterator: AnyIterator<TimeInterval> {
switch self {
case .immediate:
return AnyIterator { .zero }
case .constant(let constant):
return AnyIterator { constant }
}
}
}
struct RetryStrategy {
var maxRetries: Int
var waitStrategy: WaitStrategy
var waitTimerType: DelayTimerType
}
class RetryOperation<OperationType, Success, Failure: Error>: AsyncOperation, OutputOperation
where OperationType: OutputOperation, OperationType.Output == Result<Success, Failure>
{
typealias Output = OperationType.Output
private let operationQueue = OperationQueue()
private let producer: () -> OperationType
private let delayIterator: AnyIterator<TimeInterval>
private var retryCount: Int = 0
private let retryStrategy: RetryStrategy
private var childConfigurator: ((OperationType) -> Void)?
init(underlyingQueue: DispatchQueue? = nil, strategy: RetryStrategy, producer: @escaping () -> OperationType) {
operationQueue.underlyingQueue = underlyingQueue
delayIterator = strategy.waitStrategy.iterator
retryStrategy = strategy
self.producer = producer
}
override func main() {
retry()
}
override func operationDidCancel() {
operationQueue.cancelAllOperations()
}
private func retry(delay: TimeInterval? = nil) {
let child = producer()
child.addDidFinishBlockObserver { [weak self] (operation) in
guard let self = self else { return }
// Operation finished without output set?
guard let result = operation.output else {
self.finish()
return
}
self.synchronized {
guard case .failure(let error) = result,
let delay = self.delayIterator.next(),
self.shouldRetry(error: error) else {
self.finish(with: result)
return
}
self.retryCount += 1
self.retry(delay: delay)
}
}
synchronized {
childConfigurator?(child)
}
if let delay = delay {
let delayOperation = DelayOperation(delay: delay, timerType: retryStrategy.waitTimerType)
child.addDependency(delayOperation)
operationQueue.addOperation(delayOperation)
}
operationQueue.addOperation(child)
}
private func setChildConfigurator(_ body: @escaping (OperationType) -> Void) {
synchronized {
self.childConfigurator = body
}
}
private func shouldRetry(error: Failure) -> Bool {
return retryCount < retryStrategy.maxRetries && !self.isCancelled
}
}
extension RetryOperation: InputOperation where OperationType: InputOperation {
typealias Input = OperationType.Input
func operationDidSetInput(_ input: OperationType.Input?) {
setChildConfigurator { (child) in
child.input = input
}
}
}
|