Fixes #55 Use non-blocking Post when publishing off the UI thread#56
Conversation
PublishEvent now dispatches with the non-blocking SynchronizationContext.Post only when the event is published from a thread other than the one owning the context. When the publisher is already on the context thread (the UI thread), it keeps using the blocking Send so handlers run inline and any state they touch is updated before PublishEvent returns - preserving the existing synchronous semantics for UI-thread publishers while no longer blocking background threads on the UI. Adds tests covering both the on-context-thread (Send) and off-context-thread (Post) dispatch paths.
|
Warning Review limit reached
More reviews will be available in 46 minutes and 35 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
ChangesThread-aware Send/Post dispatch in EventPublisher
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
It turns out that there is some merit to using this Post strategy now. We also found that there are instances where the event publish is expecting to be synchronous, so in the majority of cases that's going to be preserved. I added an issue in MoBi to find and fix these instances, as well as find instances in core, pksim. |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/OSPSuite.Utility/Events/EventPublisher.cs (1)
40-56: 🎯 Functional Correctness | 🟠 MajorUse an explicit thread-affinity check here.
SynchronizationContext.Current == _contextonly compares instances; another thread can install the same context and incorrectly take the blockingSendpath. Capture the owning thread/dispatcher explicitly instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/OSPSuite.Utility/Events/EventPublisher.cs` around lines 40 - 56, The thread-affinity check in EventPublisher currently relies on SynchronizationContext.Current == _context, which can misidentify the calling thread if the same context instance is installed elsewhere. Update the publish/dispatch logic in EventPublisher to use an explicit owner-thread or dispatcher affinity check captured with _context, and use that check to decide between _context.Send and _context.Post. Keep the receiver dispatch path and _exceptionManager.Execute behavior unchanged, but base the blocking/non-blocking choice on the owning thread identity rather than context instance equality.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/OSPSuite.Utility.Tests/EventPublisherSpecs.cs`:
- Around line 54-55: The worker-thread join in the EventPublisherSpecs test is
unbounded and can hang the suite if PublishEvent blocks or deadlocks. Update the
test around thread.Start and thread.Join to use a bounded join with a timeout
and fail the test explicitly if the worker does not finish, so the blocking
behavior is still verified without risking an indefinite stall.
---
Outside diff comments:
In `@src/OSPSuite.Utility/Events/EventPublisher.cs`:
- Around line 40-56: The thread-affinity check in EventPublisher currently
relies on SynchronizationContext.Current == _context, which can misidentify the
calling thread if the same context instance is installed elsewhere. Update the
publish/dispatch logic in EventPublisher to use an explicit owner-thread or
dispatcher affinity check captured with _context, and use that check to decide
between _context.Send and _context.Post. Keep the receiver dispatch path and
_exceptionManager.Execute behavior unchanged, but base the blocking/non-blocking
choice on the owning thread identity rather than context instance equality.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 59003ffe-a4cf-4f1b-9f6e-0b9708860f2f
📒 Files selected for processing (2)
src/OSPSuite.Utility/Events/EventPublisher.cstests/OSPSuite.Utility.Tests/EventPublisherSpecs.cs
Fail the test with a clear TimeoutException if PublishEvent never returns on the worker thread, instead of hanging the suite on an unbounded Join. Addresses PR review.
What
EventPublisher.PublishEventpreviously dispatched every listener via the blockingSynchronizationContext.Send. This change makes dispatch thread-aware:Send, so handlers run inline and any state they touch is updated beforePublishEventreturns. This preserves the existing synchronous semantics (read-after-publish, ordering) for UI-thread publishers, andSendto the UI thread from the UI thread is effectively a direct call anyway.Post, so the background thread is no longer blocked waiting on the UI thread.The thread check is
SynchronizationContext.Current == _context— "is the current thread the one this publisher dispatches to?" — computed once per call.Why
Blocking the publisher on the UI thread for every listener can stall background work and risks UI-thread contention. Switching unconditionally to
Postwould have been a behavioral change for UI-thread publishers that rely on synchronous completion, so the dispatch path is chosen per call based on the publishing thread.Tests
Added coverage for both branches:
When_publishing_an_event_from_the_thread_that_owns_the_synchronization_context→ assertsSend(notPost) and that the listener is notified.When_publishing_an_event_from_a_thread_that_does_not_own_the_synchronization_context→ assertsPost(notSend) and notification.A
RecordingSynchronizationContextruns both paths synchronously to keep observations deterministic, and a helper runs each publish on a throwaway thread with a chosen ambient context so the runner thread'sSynchronizationContext.Currentis never mutated. All 317 tests pass locally.Fixes #55
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests