Skip to content

Commit 0736102

Browse files
RootCore crash (#3638)
* fix crash deinit outside MainActor * Alternative: Mark AnyCancellable closures as sendable. --------- Co-authored-by: Brandon Williams <mbrandonw@hey.com>
1 parent 292546d commit 0736102

File tree

4 files changed

+24
-6
lines changed

4 files changed

+24
-6
lines changed

Sources/ComposableArchitecture/Core.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ final class RootCore<Root: Reducer>: Core {
128128
}
129129
boxedTask.wrappedValue = task
130130
tasks.withValue { $0.append(task) }
131-
self.effectCancellables[uuid] = AnyCancellable {
131+
self.effectCancellables[uuid] = AnyCancellable { @Sendable in
132132
task.cancel()
133133
}
134134
}
@@ -169,7 +169,7 @@ final class RootCore<Root: Reducer>: Core {
169169
self?.effectCancellables[uuid] = nil
170170
}
171171
tasks.withValue { $0.append(task) }
172-
self.effectCancellables[uuid] = AnyCancellable {
172+
self.effectCancellables[uuid] = AnyCancellable { @Sendable in
173173
task.cancel()
174174
}
175175
}

Sources/ComposableArchitecture/Effects/Cancellation.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ extension Effect {
5858

5959
let cancellable = LockIsolated<AnyCancellable?>(nil)
6060
cancellable.setValue(
61-
AnyCancellable {
61+
AnyCancellable { @Sendable in
6262
_cancellationCancellables.withValue {
6363
cancellationSubject.send(())
6464
cancellationSubject.send(completion: .finished)
@@ -179,7 +179,7 @@ extension Effect {
179179
$0.cancel(id: id, path: navigationIDPath)
180180
}
181181
let task = Task { try await operation() }
182-
let cancellable = AnyCancellable { task.cancel() }
182+
let cancellable = AnyCancellable { @Sendable in task.cancel() }
183183
$0.insert(cancellable, at: id, path: navigationIDPath)
184184
return (cancellable, task)
185185
}

Sources/ComposableArchitecture/Effects/Publisher.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public struct _EffectPublisher<Action>: Publisher {
3636
defer { subscriber.send(completion: .finished) }
3737
await operation(Send { subscriber.send($0) })
3838
}
39-
return AnyCancellable {
39+
return AnyCancellable { @Sendable in
4040
task.cancel()
4141
}
4242
}

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)