-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathAsyncSwitchToLatestSequence.swift
More file actions
358 lines (323 loc) · 12.3 KB
/
Copy pathAsyncSwitchToLatestSequence.swift
File metadata and controls
358 lines (323 loc) · 12.3 KB
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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
//
// 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)
case cancelled
var isCancelled: Bool {
if case .cancelled = self {
return true
}
return false
}
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>?)
case stop
}
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 {
guard !Task.isCancelled else { return }
let childIterator = child.makeAsyncIterator()
let decision = state.withCriticalRegion { state -> BaseDecision in
guard !state.base.isCancelled else { return .stop }
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 .stop:
return
case .cancelPreviousChildTask(let task):
task?.cancel()
case .resumeNext(let continuation, let childTask):
continuation.resume(returning: childTask)
}
}
let decision = state.withCriticalRegion { state -> BaseDecision in
guard !state.base.isCancelled else { return .stop }
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 .stop:
return
case .cancelPreviousChildTask:
break
case .resumeNext(let continuation, let childTask):
continuation.resume(returning: childTask)
}
} catch {
let decision = state.withCriticalRegion { state -> BaseDecision in
guard !state.base.isCancelled else { return .stop }
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 .stop:
return
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))
}
}
}
static func cancel(baseTask: Task<Void, Never>?, state: ManagedCriticalState<State>) {
let cancellation = state.withCriticalRegion { state -> (
continuation: UnsafeContinuation<Task<ChildValue?, Never>?, Never>?,
childTask: Task<ChildValue?, Never>?
) in
let continuation: UnsafeContinuation<Task<ChildValue?, Never>?, Never>?
if case .waitingForChildIterator(let waiting) = state.base {
continuation = waiting
} else {
continuation = nil
}
let childTask = state.childTask
// Claim the waiter and discard retained iterators before invoking any cancellation handlers.
state.base = .cancelled
state.childTask = nil
return (continuation, childTask)
}
cancellation.continuation?.resume(returning: nil)
baseTask?.cancel()
cancellation.childTask?.cancel()
}
public mutating func next() async rethrows -> Element? {
guard !Task.isCancelled else {
Self.cancel(baseTask: self.baseTask, state: self.state)
return nil
}
self.startBase()
return try await withTaskCancellationHandler {
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 .cancelled:
return .immediatelyResume(nil)
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
state.childTask = nil
if state.base.isCancelled {
return .returnFinish
} else 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()
}
}
} onCancel: { [baseTask, state] in
Self.cancel(baseTask: baseTask, state: state)
}
}
}
}
extension AsyncSwitchToLatestSequence: Sendable where Base: Sendable {}