Repository navigation
fix: deliver provider events in order, outside handler locks - #351
Conversation
- An event emitted for a provider that is already being dispatched on this call stack is queued and delivered after the current one, in order (#332). Events for other providers dispatch right away. - Provider events record their status when delivered (5.3.5). The SDK's own READY/ERROR records it when sent, so set_provider_and_wait from the provider's own handler returns with the provider READY. - A READY/ERROR the SDK sent for an earlier registration of the same provider is dropped. - Client handlers run outside the client-handler lock, so they can add or remove handlers and emit events. add_client_handler validates like the API, through one shared validator. - A queued event that fails is logged instead of failing the emit or init that was dispatching. - An API handler added after the fact runs once per provider, not once per bound domain. Signed-off-by: Jose Colella <jose.colella@gusto.com> Co-authored-by: Leo Romanovsky <57361+leoromanovsky@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: 🔵 Low · up to
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #351 +/- ##
==========================================
+ Coverage 99.54% 99.66% +0.11%
==========================================
Files 32 32
Lines 887 906 +19
==========================================
+ Hits 883 903 +20
+ Misses 4 3 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Signed-off-by: Jose Colella <jose.colella@gusto.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| @provider_state_registry.update_state_from_event(provider, event_type, details) | ||
| queues = (Thread.current[@dispatch_key] ||= {}.compare_by_identity) | ||
| if (queue = queues[provider]) | ||
| queue << [event_type, details.dup, token] |
| provider_name: provider_name | ||
| }.merge(details) | ||
| def deliver_provider_event(provider, event_type, details, token) | ||
| return unless accept_provider_event(provider, event_type, details, token, record_status: token.nil?) |
| @provider_mutex.synchronize do | ||
| return false unless provider_bound?(provider) | ||
| # An SDK READY/ERROR from an earlier registration of this provider is stale | ||
| return false unless token.nil? || @init_attempts[provider].equal?(token) |

Summary
deadlock; recursive lockingerror being logged.Behavior changes worth noting
initrecords its status when sent. That wayset_provider_and_waitcalled from the provider's own handler (for example, an ERROR handler retryinginit) returns with the provider READY.client.add_handlernow raisesArgumentErrorfor an unknown event type or a missing handler, the same as the API-leveladd_handler. Before, a missing handler was accepted and only failed when an event fired.initthat was dispatching.mainit raisesSystemStackError.StandardError, raised by a handler, drops the events queued behind it on that call stack.Merging
Co-authored-by: Leo Romanovsky <57361+leoromanovsky@users.noreply.github.com>to the squash message, since Reentrant provider events reach client callbacks out of order #332's reproduction and proposed direction are his.🤖 Generated with Claude Code