-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathAsyncSwitchToLatestSequence.swift
311 lines (279 loc) · 10.6 KB
/
AsyncSwitchToLatestSequence.swift
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
//
// AsyncSwitchToLatestSequence.swift
//
//
// Created by Thibault Wittemberg on 04/01/2022.
//
public extension AsyncSequence where Element: AsyncSequence {
/// Republishes elements sent by the most recently received async sequence.
///
/// ```
/// let sourceSequence = AsyncSequences.From([1, 2, 3])
/// let mappedSequence = sourceSequence.map { element in ["a\(element)", "b\(element)"].async }
/// let switchedSequence = mappedSequence.switchToLatest()
///
/// for try await element in switchedSequence {
/// print(element)
/// }
///
/// // will print:
/// a3, b3
/// ```
///
/// - Returns: The async sequence that republishes elements sent by the most recently received async sequence.
func switchToLatest() -> AsyncSwitchToLatestSequence<Self> where Self.Element.Element: Sendable {
AsyncSwitchToLatestSequence<Self>(self)
}
}
public struct AsyncSwitchToLatestSequence<Base: AsyncSequence>: AsyncSequence
where Base.Element: AsyncSequence, Base: Sendable, Base.Element.Element: Sendable, Base.Element.AsyncIterator: Sendable {
public typealias Element = Base.Element.Element
public typealias AsyncIterator = Iterator
let base: Base
init(_ base: Base) {
self.base = base
}
public func makeAsyncIterator() -> Iterator {
Iterator(self.base)
}
public struct Iterator: AsyncIteratorProtocol {
enum BaseState {
case notStarted
case idle
case waitingForChildIterator(UnsafeContinuation<Task<ChildValue?, Never>?, Never>)
case newChildIteratorAvailable(Result<Base.Element.AsyncIterator, Error>)
case processingChildIterator(Result<Base.Element.AsyncIterator, Error>)
case finished(Result<Base.Element.AsyncIterator, Error>?)
case failed(Error)
var isFinished: Bool {
if case .finished = self {
return true
}
return false
}
var isNewAvailableChildIterator: Bool {
if case .newChildIteratorAvailable = self {
return true
}
return false
}
var childIterator: Result<Base.Element.AsyncIterator, Error>? {
switch self {
case
.newChildIteratorAvailable(let childIterator),
.processingChildIterator(let childIterator):
return childIterator
case .finished(let childIterator):
return childIterator
case .failed(let error):
return .failure(error)
default:
return nil
}
}
}
struct State {
var childTask: Task<ChildValue?, Never>?
var base: BaseState
static var initial: State {
State(childTask: nil, base: .notStarted)
}
}
enum BaseDecision {
case resumeNext(UnsafeContinuation<Task<ChildValue?, Never>?, Never>, Task<ChildValue?, Never>?)
case cancelPreviousChildTask(Task<ChildValue?, Never>?)
}
enum NextDecision {
case immediatelyResume(Task<ChildValue?, Never>)
case suspend
}
enum PostElementDecision {
case returnElement(Result<Element, Error>)
case returnError(Result<Element, Error>)
case returnFinish
case pass
}
enum ChildValue: Sendable {
case element(Base.Element.AsyncIterator?, Result<Element?, Error>)
case cancelled
}
var baseTask: Task<Void, Never>?
let base: Base
let state: ManagedCriticalState<State>
init(_ base: Base) {
self.state = ManagedCriticalState(.initial)
self.base = base
}
mutating func startBase() {
let wasBaseAlreadyStarted = self.state.withCriticalRegion { state -> Bool in
switch state.base {
case .notStarted:
state.base = .idle
return false
default:
return true
}
}
guard !wasBaseAlreadyStarted else { return }
self.baseTask = Task { [base, state] in
do {
for try await child in base {
let childIterator = child.makeAsyncIterator()
let decision = state.withCriticalRegion { state -> BaseDecision in
switch state.base {
case .waitingForChildIterator(let continuation):
state.base = .processingChildIterator(.success(childIterator))
let childTask = Self.makeChildTask(childIterator: .success(childIterator))
state.childTask = childTask
return .resumeNext(continuation, childTask)
default:
state.base = .newChildIteratorAvailable(.success(childIterator))
return .cancelPreviousChildTask(state.childTask)
}
}
switch decision {
case .cancelPreviousChildTask(let task):
task?.cancel()
case .resumeNext(let continuation, let childTask):
continuation.resume(returning: childTask)
}
}
let decision = state.withCriticalRegion { state -> BaseDecision in
switch state.base {
case .waitingForChildIterator(let continuation):
state.base = .finished(nil)
return .resumeNext(continuation, nil)
default:
state.base = .finished(state.base.childIterator)
return .cancelPreviousChildTask(nil)
}
}
switch decision {
case .cancelPreviousChildTask:
break
case .resumeNext(let continuation, let childTask):
continuation.resume(returning: childTask)
}
} catch {
let decision = state.withCriticalRegion { state -> BaseDecision in
switch state.base {
case .waitingForChildIterator(let continuation):
state.base = .failed(error)
let childTask = Self.makeChildTask(childIterator: .failure(error))
state.childTask = childTask
return .resumeNext(continuation, childTask)
default:
state.base = .failed(error)
return .cancelPreviousChildTask(state.childTask)
}
}
switch decision {
case .cancelPreviousChildTask(let task):
task?.cancel()
case .resumeNext(let continuation, let childTask):
continuation.resume(returning: childTask)
}
}
}
}
static func makeChildTask(childIterator: Result<Base.Element.AsyncIterator, Error>?) -> Task<ChildValue?, Never> {
Task {
do {
try Task.checkCancellation()
guard var iterator = try childIterator?.get() else { return nil }
let element = try await iterator.next()
try Task.checkCancellation()
return .element(iterator, .success(element))
} catch is CancellationError {
return .cancelled
} catch {
return .element(nil, .failure(error))
}
}
}
public mutating func next() async rethrows -> Element? {
guard !Task.isCancelled else { return nil }
self.startBase()
return try await withTaskCancellationHandler { [baseTask, state] in
baseTask?.cancel()
state.withCriticalRegion {
$0.childTask?.cancel()
}
} operation: {
while true {
let childTask = await withUnsafeContinuation { [state] (continuation: UnsafeContinuation<Task<ChildValue?, Never>?, Never>) in
let decision = state.withCriticalRegion { state -> NextDecision in
switch state.base {
case .newChildIteratorAvailable(let childIterator):
state.base = .processingChildIterator(childIterator)
let childTask = Self.makeChildTask(childIterator: childIterator)
state.childTask = childTask
return .immediatelyResume(childTask)
case .processingChildIterator(let childIterator):
let childTask = Self.makeChildTask(childIterator: childIterator)
state.childTask = childTask
return .immediatelyResume(childTask)
case .finished(let childIterator):
let childTask = Self.makeChildTask(childIterator: childIterator)
state.childTask = childTask
return .immediatelyResume(childTask)
case .failed(let error):
let childTask = Self.makeChildTask(childIterator: .failure(error))
state.childTask = childTask
return .immediatelyResume(childTask)
default:
state.base = .waitingForChildIterator(continuation)
return .suspend
}
}
switch decision {
case .suspend:
break
case .immediatelyResume(let childTask):
continuation.resume(returning: childTask)
}
}
let value = await childTask?.value
let decision = state.withCriticalRegion { state -> PostElementDecision in
if state.base.isNewAvailableChildIterator {
return .pass
} else {
switch value {
case .element(_, .success(nil)) where state.base.isFinished:
return .returnFinish
case .element(_, .success(nil)) where !state.base.isFinished:
state.base = .idle
return .pass
case .element(_, .failure(let error)):
state.base = .failed(error)
return .returnError(.failure(error))
case .element(.some(let childIterator), .success(.some(let element))) where state.base.isFinished:
state.base = .finished(.success(childIterator))
return .returnElement(.success(element))
case .element(.some(let childIterator), .success(let element)) where !state.base.isFinished:
state.base = .processingChildIterator(.success(childIterator))
return .returnElement(.success(element!))
case .cancelled where state.base.childIterator != nil:
return .pass
default:
return .returnFinish
}
}
}
switch decision {
case .pass:
continue
case .returnFinish:
return nil
case .returnError(let error):
self.baseTask?.cancel()
return try error._rethrowGet()
case .returnElement(let element):
return try element._rethrowGet()
}
}
}
}
}
}
extension AsyncSwitchToLatestSequence: Sendable where Base: Sendable {}