Skip to content

Commit ca2ac60

Browse files
authored
Tolerate cancelling before we create our task (#250)
# Motivation We switched the point in time when we create tasks to the first call to `next()`. This had the side effect that we can now be cancelled before we even created our task. Which by itself is fine but we didn't handle it in the state machines. # Modification Adapt the state machines of `debounce`, `zip`, `merge` and `combineLatest` to handle cancellation before `next()`. # Result We shouldn't run into these preconditions anymore.
1 parent bf0ec24 commit ca2ac60

File tree

8 files changed

+54
-8
lines changed

8 files changed

+54
-8
lines changed

Sources/AsyncAlgorithms/CombineLatest/CombineLatestStateMachine.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,9 @@ struct CombineLatestStateMachine<
585585
mutating func cancelled() -> CancelledAction? {
586586
switch self.state {
587587
case .initial:
588-
preconditionFailure("Internal inconsistency current state \(self.state) and received cancelled()")
588+
state = .finished
589+
590+
return .none
589591

590592
case .waitingForDemand(let task, let upstreams, _):
591593
// The downstream task got cancelled so we need to cancel our upstream Task

Sources/AsyncAlgorithms/Debounce/DebounceStateMachine.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -583,9 +583,8 @@ struct DebounceStateMachine<Base: AsyncSequence, C: Clock> {
583583
mutating func cancelled() -> CancelledAction? {
584584
switch self.state {
585585
case .initial:
586-
// Since we are transitioning to `merging` before we return from `makeAsyncIterator`
587-
// this can never happen
588-
preconditionFailure("Internal inconsistency current state \(self.state) and received cancelled()")
586+
state = .finished
587+
return .none
589588

590589
case .waitingForDemand:
591590
// We got cancelled before we event got any demand. This can happen if a cancelled task

Sources/AsyncAlgorithms/Merge/MergeStateMachine.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -465,9 +465,11 @@ struct MergeStateMachine<
465465
mutating func cancelled() -> CancelledAction {
466466
switch state {
467467
case .initial:
468-
// Since we are transitioning to `merging` before we return from `makeAsyncIterator`
469-
// this can never happen
470-
preconditionFailure("Internal inconsistency current state \(self.state) and received cancelled()")
468+
// Since we are only transitioning to merging when the task is started we
469+
// can be cancelled already.
470+
state = .finished
471+
472+
return .none
471473

472474
case let .merging(task, _, upstreamContinuations, _, .some(downstreamContinuation)):
473475
// The downstream Task got cancelled so we need to cancel our upstream Task

Sources/AsyncAlgorithms/Zip/ZipStateMachine.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,9 @@ struct ZipStateMachine<
460460
mutating func cancelled() -> CancelledAction? {
461461
switch self.state {
462462
case .initial:
463-
preconditionFailure("Internal inconsistency current state \(self.state) and received cancelled()")
463+
state = .finished
464+
465+
return .none
464466

465467
case .waitingForDemand(let task, let upstreams):
466468
// The downstream task got cancelled so we need to cancel our upstream Task

Tests/AsyncAlgorithmsTests/TestCombineLatest.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,16 @@ final class TestCombineLatest2: XCTestCase {
318318
task.cancel()
319319
wait(for: [finished], timeout: 1.0)
320320
}
321+
322+
func test_combineLatest_when_cancelled() async {
323+
let t = Task {
324+
try? await Task.sleep(nanoseconds: 1_000_000_000)
325+
let c1 = Indefinite(value: "test1").async
326+
let c2 = Indefinite(value: "test1").async
327+
for await _ in combineLatest(c1, c2) {}
328+
}
329+
t.cancel()
330+
}
321331
}
322332

323333
final class TestCombineLatest3: XCTestCase {

Tests/AsyncAlgorithmsTests/TestDebounce.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,15 @@ final class TestDebounce: XCTestCase {
6868
let throwingDebounce = [1].async.map { try throwOn(2, $0) }.debounce(for: .zero, clock: ContinuousClock())
6969
for try await _ in throwingDebounce {}
7070
}
71+
72+
func test_debounce_when_cancelled() async throws {
73+
guard #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) else { throw XCTSkip("Skipped due to Clock/Instant/Duration availability") }
74+
75+
let t = Task {
76+
try? await Task.sleep(nanoseconds: 1_000_000_000)
77+
let c1 = Indefinite(value: "test1").async
78+
for await _ in c1.debounce(for: .seconds(1), clock: .continuous) {}
79+
}
80+
t.cancel()
81+
}
7182
}

Tests/AsyncAlgorithmsTests/TestMerge.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,16 @@ final class TestMerge2: XCTestCase {
191191
task.cancel()
192192
wait(for: [finished], timeout: 1.0)
193193
}
194+
195+
func test_merge_when_cancelled() async {
196+
let t = Task {
197+
try? await Task.sleep(nanoseconds: 1_000_000_000)
198+
let c1 = Indefinite(value: "test1").async
199+
let c2 = Indefinite(value: "test1").async
200+
for await _ in merge(c1, c2) {}
201+
}
202+
t.cancel()
203+
}
194204
}
195205

196206
final class TestMerge3: XCTestCase {

Tests/AsyncAlgorithmsTests/TestZip.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,16 @@ final class TestZip2: XCTestCase {
158158
task.cancel()
159159
wait(for: [finished], timeout: 1.0)
160160
}
161+
162+
func test_zip_when_cancelled() async {
163+
let t = Task {
164+
try? await Task.sleep(nanoseconds: 1_000_000_000)
165+
let c1 = Indefinite(value: "test1").async
166+
let c2 = Indefinite(value: "test1").async
167+
for await _ in zip(c1, c2) {}
168+
}
169+
t.cancel()
170+
}
161171
}
162172

163173
final class TestZip3: XCTestCase {

0 commit comments

Comments
 (0)