From 640f53a9b563fed949695e1639b0934e8d3ba2e4 Mon Sep 17 00:00:00 2001 From: Ian Keen Date: Mon, 6 Sep 2021 16:42:31 -0700 Subject: [PATCH 1/2] Add threading warning to Store.send Any async Effect (including fireAndForget) could result in a runtime crash when updating internal state like `effectCancellables` This adds a debug warning to let users know --- Sources/ComposableArchitecture/Store.swift | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Sources/ComposableArchitecture/Store.swift b/Sources/ComposableArchitecture/Store.swift index d5cb4faf6f66..d8367fa98867 100644 --- a/Sources/ComposableArchitecture/Store.swift +++ b/Sources/ComposableArchitecture/Store.swift @@ -382,8 +382,41 @@ public final class Store { var didComplete = false let uuid = UUID() + + #if DEBUG + let initalThread = Thread.current + initalThread.threadDictionary[uuid] = true + #endif + let effectCancellable = effect.sink( receiveCompletion: { [weak self] _ in + // + #if DEBUG + if Thread.current.threadDictionary[uuid] == nil { + breakpoint( + """ + --- + Warning: Store.send + + The Store class is not thread-safe, and so all interactions with an instance of Store + (including all of its scopes and derived ViewStores) must be done on the same thread. + + \(debugCaseOutput(action)) has produced an Effect that was completed on a different thread \ + from the one it was executed on. + + Starting thread: \(initalThread) + Final thread: \(Thread.current) + + Possible fixes for this are: + + * Add a .receive(on:) to the Effect to ensure it completes on this Stores correct thread. + """ + ) + } + + Thread.current.threadDictionary[uuid] = nil + #endif + didComplete = true self?.effectCancellables[uuid] = nil }, From ee34b8fb45155ce1a0bb9c1cf581a4d453d7eb41 Mon Sep 17 00:00:00 2001 From: Ian Keen Date: Mon, 6 Sep 2021 16:44:39 -0700 Subject: [PATCH 2/2] Remove comment --- Sources/ComposableArchitecture/Store.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/ComposableArchitecture/Store.swift b/Sources/ComposableArchitecture/Store.swift index d8367fa98867..86b0c4b862cd 100644 --- a/Sources/ComposableArchitecture/Store.swift +++ b/Sources/ComposableArchitecture/Store.swift @@ -390,7 +390,6 @@ public final class Store { let effectCancellable = effect.sink( receiveCompletion: { [weak self] _ in - // #if DEBUG if Thread.current.threadDictionary[uuid] == nil { breakpoint(