-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Warn if send is called after .run completes #1900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Warn if send is called after .run completes #1900
Conversation
When an Effect.run is converted into a publisher, we already end up discarding `send`s sent after returning. Similarly, `*Task.finish()` will fail to track send()s after other in-flight effects complete. This is effectively UB so let's warn about it.
@@ -417,9 +417,36 @@ public final class Store<State, Action> { | |||
} | |||
case let .run(priority, operation): | |||
tasks.wrappedValue.append( | |||
Task(priority: priority) { | |||
Task(priority: priority) { @MainActor in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really want to mark it as @MainActor
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need the reads and writes to isCompleted
to be serialized, and since the Send
closure is @MainActor
it's sufficient to make this one @MainActor
too for that purpose. This doesn't mean that operation
will run on the MainActor, but rather just the synchronous parts of the task (i.e. the isCompleted = true
). We're already doing a MainActor hop in the Publisher case so this isn't straying from established practice.
For clarity, so the problem is in user code not taking part in structured concurrency of |
thanks, yeah I wanted to mention "structured concurrency" in the runtime warning but figured it's better to avoid jargon. FWIW there are a number of other ways to trigger this such as calling a completion/GCD-based API (eg |
Hi @kabiroberai, thanks for the PR! Overall I think this looks good! I took a pass at playing with the messaging, but Stephen and I will discuss more. I also split the test to focus just on the publisher API versus the pure async/await API. We should be able to merge this soon. |
@mbrandonw btw I think CI is failing because the check is guarded by |
🤦♂️ thanks for pointing that out. Was confusing me. Yeah I think it's OK to do this check in release. We already have other runtime warnings in release. Want to fix that up if you have the time? |
@mbrandonw so it looks like the |
Sorry again, I should have looked into this more closely. You are right, |
Finally got tests to pass 😅. Custom executors can't come soon enough. @stephencelis and I will chat about this today and hopefully get it merged! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! Thanks for tackling this!
Calling
send
after.run
returns can result in broken assumptions and discarded actions..run
is directly sent into a Store, thesend
calls will work but they won't be tracked by[ViewStoreTask|TestStoreTask|TestStore].finish()
EffectTask.run
is converted into a Publisher by applying a Combine operator, the resulting Effect will entirely ignore actions sent after the async closure returns, sinceEffectTask.publisher
marks the effect as completed at that point.This PR adds a runtime warning that's emitted if
send
is called after the.run
closure returns, formally marking this as undefined behavior.