Skip to content

Commit f6795a4

Browse files
committed
fix crash deinit outside MainActor
1 parent 292546d commit f6795a4

File tree

2 files changed

+26
-7
lines changed

2 files changed

+26
-7
lines changed

Sources/ComposableArchitecture/Core.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,7 @@ final class RootCore<Root: Reducer>: Core {
128128
}
129129
boxedTask.wrappedValue = task
130130
tasks.withValue { $0.append(task) }
131-
self.effectCancellables[uuid] = AnyCancellable {
132-
task.cancel()
133-
}
131+
self.effectCancellables[uuid] = self.effectCancellable(task: task)
134132
}
135133
case let .run(priority, operation):
136134
withEscapedDependencies { continuation in
@@ -169,9 +167,7 @@ final class RootCore<Root: Reducer>: Core {
169167
self?.effectCancellables[uuid] = nil
170168
}
171169
tasks.withValue { $0.append(task) }
172-
self.effectCancellables[uuid] = AnyCancellable {
173-
task.cancel()
174-
}
170+
self.effectCancellables[uuid] = self.effectCancellable(task: task)
175171
}
176172
}
177173
}
@@ -193,6 +189,11 @@ final class RootCore<Root: Reducer>: Core {
193189
}
194190
}
195191
}
192+
193+
nonisolated private func effectCancellable<Success, Failure>(task: Task<Success, Failure>) -> AnyCancellable {
194+
AnyCancellable { task.cancel() }
195+
}
196+
196197
private actor DefaultIsolation {}
197198
}
198199

Tests/ComposableArchitectureTests/StoreLifetimeTests.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ final class StoreLifetimeTests: BaseTCATestCase {
6969
}
7070

7171
@MainActor
72-
func testStoreDeinit_RunningEffect() async {
72+
func testStoreDeinit_RunningEffect_MainActor() async {
7373
Logger.shared.isEnabled = true
7474
let effectFinished = self.expectation(description: "Effect finished")
7575
do {
@@ -95,6 +95,24 @@ final class StoreLifetimeTests: BaseTCATestCase {
9595
await self.fulfillment(of: [effectFinished], timeout: 0.5)
9696
}
9797

98+
func testStoreDeinit_RunningEffect() async {
99+
let effectFinished = self.expectation(description: "Effect finished")
100+
do {
101+
let store = await Store<Void, Void>(initialState: ()) {
102+
Reduce { state, _ in
103+
.run { _ in
104+
try? await Task.never()
105+
effectFinished.fulfill()
106+
}
107+
}
108+
}
109+
await store.send(())
110+
_ = store
111+
}
112+
113+
await self.fulfillment(of: [effectFinished], timeout: 0.5)
114+
}
115+
98116
@MainActor
99117
func testStoreDeinit_RunningCombineEffect() async {
100118
Logger.shared.isEnabled = true

0 commit comments

Comments
 (0)