Repository navigation
feat: pass the bound domain to provider init and support domain-scoped providers - #345
Conversation
…ped binding Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com> Signed-off-by: Jose Colella <jose.colella@gusto.com>
Signed-off-by: Jonathan Norris <jonathan.norris@dynatrace.com> Signed-off-by: Jose Colella <jose.colella@gusto.com>
- Read the init signature from the provider object, so delegators, module-defined init and forwarding signatures get the context instead of raising. Pass domain: only to an explicit keyword or named **opts. - Evaluate domain_scoped? before taking the provider lock and detect it via respond_to?; warn when a domain-scoped init can't take domain:. - Track one init attempt per registration: a failed init is retried on the next set, a running init is waited on instead of started twice, and a provider is never reported READY without a successful init. - Settle the attempt before READY/ERROR handlers run so they can register the provider again, fail attempts that no init took over, and release waiters on shutdown. - Test providers subclass InMemoryProvider; drop the duplicate legacy test and the one-use support class. Signed-off-by: Jose Colella <jose.colella@gusto.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedThis review ran on the open-source allowance, not this organization's plan, because the pull request author doesn't have an assigned seat. Waiting won't change this — ask an organization admin to assign them a seat, or add seats in Billing if every seat is already assigned, then retry. Next included review available in 59 minutes. View limit details
Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #345 +/- ##
==========================================
+ Coverage 99.52% 99.54% +0.02%
==========================================
Files 32 32
Lines 841 879 +38
==========================================
+ Hits 837 875 +38
Misses 4 4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟡 Changes recommended
Initialization waiters can remain blocked after provider replacement and shutdown, and synchronous provider-event handlers can deadlock when registering the same provider.
2 open issues
What changed in this PR
This PR advances OpenFeature specification compliance by passing bound domains to provider initialization and enforcing single-domain binding for providers that opt in.
Changes:
- Adds domain-aware initialization while preserving legacy provider signatures.
- Tracks initialization outcomes to support retries and waiting registrations.
- Adds compatibility and lifecycle tests, provider documentation, and type signatures.
| File | Description |
|---|---|
spec/specification/flag_evaluation_api_spec.rb |
Tests initialization outcomes, retries, waiting, and handler reentrancy. |
spec/specification/domain_scoped_provider_spec.rb |
Tests domain binding restrictions and initialization compatibility. |
spec/open_feature/sdk/configuration_spec.rb |
Tests domain arguments and varied provider initialization signatures. |
sig/open_feature/sdk/provider.rbs |
Declares domain-aware lifecycle and domain-scoped interfaces. |
sig/open_feature/sdk/configuration.rbs |
Adds initialization and domain-binding helper signatures. |
README.md |
Documents optional initialization arguments and domain-scoped providers. |
lib/open_feature/sdk/configuration.rb |
Implements domain binding validation and initialization-attempt tracking. |
Review effort: Balanced
| if old_provider && !old_provider.equal?(provider) | ||
| needs_shutdown = !provider_bound?(old_provider) | ||
| if needs_shutdown | ||
| @init_attempts.delete(old_provider) |
There was a problem hiding this comment.
Fixed in 95f9d98. When the last binding of a provider is removed, its attempt is now abandoned under the lock: a running attempt is marked failed and its queue closed. reset does the same. A waiter then raises ProviderInitializationError ("initialization did not complete") instead of staying blocked. New test: "replacing a provider releases a waiter on its unfinished init".
| elsif wait_for_init | ||
| # Provider already active; no init needed but still dispatch READY | ||
| # Already registered: wait for an init still in flight, then report its outcome | ||
| attempt.done.pop |
There was a problem hiding this comment.
Fixed in 95f9d98. Each attempt now records the thread running its init. If set_provider_and_wait finds the attempt still running on the current thread, it raises ProviderInitializationError ("is still initializing on this thread") instead of waiting on itself. So it doesn't deadlock, and it never reports an unfinished init as successful. New test: "set_provider_and_wait from inside the provider's own init raises instead of deadlocking", which uses a provider that emits an event from init.

Summary
initasdomain:, ornilfor the default provider (spec 1.1.2.2, 2.4.1).domain_scoped?. Binding the same instance to a second domain then raisesArgumentError(1.1.8.1, 2.4.3). If itsinitcan't acceptdomain:, the SDK logs a warning (2.4.4).Context for reviewers
mainhas since moved past it.init's signature from the class chain, which passed the wrong arguments to delegating wrappers, to aninitdefined in a module, and to(...)or*argssignatures. It now readsprovider.method(:init).parameters. Signatures it can't read get the context only, as onmaintoday.initfailed could be reported READY when it was set again. Each registration now records its init outcome (running, succeeded or failed).domain_scoped?runs before the provider lock is taken, because provider code may call back into the SDK.Behavior changes worth noting
initagain.set_provider_and_waitstill reports READY in that case. A later PR in this stack (provider-owned lifecycle events) removes that forced READY.initfailed is initialized again the next time it is set.set_provider_and_waiton a provider whoseinitis still running waits for it, then returns or raises its result.shutdownreleases anyone waiting.init.How to review
configuration.rb:set_provider_internal,init_providerandcall_init.Merging
Co-authored-by: Jonathan Norris <jonathan.norris@dynatrace.com>to the squash message to keep his authorship.🤖 Generated with Claude Code