Skip to content

feat: pass the bound domain to provider init and support domain-scoped providers - #345

Merged
josecolella merged 3 commits into
mainfrom
feat/domain-in-provider-init
Oct 23, 2026
Merged

josecolella merged 3 commits into
mainfrom
feat/domain-in-provider-init

Conversation

@josecolella

@josecolella josecolella commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Context for reviewers

  • The first two commits are cherry-picked from feat(core): pass bound domain to provider init and enforce domain-scoped binding #298 unchanged. Its CI and lockfile commit is dropped, because main has since moved past it.
  • The third commit fixes two runtime problems in feat(core): pass bound domain to provider init and enforce domain-scoped binding #298:
    • It read init's signature from the class chain, which passed the wrong arguments to delegating wrappers, to an init defined in a module, and to (...) or *args signatures. It now reads provider.method(:init).parameters. Signatures it can't read get the context only, as on main today.
    • A provider whose init failed 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

  • Setting an already-initialized provider again, on the same or another domain, doesn't run init again. set_provider_and_wait still reports READY in that case. A later PR in this stack (provider-owned lifecycle events) removes that forced READY.
  • A provider whose init failed is initialized again the next time it is set.
  • set_provider_and_wait on a provider whose init is still running waits for it, then returns or raises its result. shutdown releases anyone waiting.
  • A READY or ERROR handler can register the same provider again. For example, an ERROR handler can retry a failed init.

How to review

  • The substance is in configuration.rb: set_provider_internal, init_provider and call_init.

Merging

  • Squash merges here use an empty body, so add Co-authored-by: Jonathan Norris <jonathan.norris@dynatrace.com> to the squash message to keep his authorship.

🤖 Generated with Claude Code

jonathannorris and others added 3 commits October 22, 2026 18:44
…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>
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

This 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.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 43924cbd-b513-4f0a-b826-0198d1da41b7
📥 Commits

Reviewing files that changed from the base of the PR and between f37850a and ac6244b.

📒 Files selected for processing: 7
  • README.md
  • lib/open_feature/sdk/configuration.rb
  • sig/open_feature/sdk/configuration.rbs
  • sig/open_feature/sdk/provider.rbs
  • spec/open_feature/sdk/configuration_spec.rb
  • spec/specification/domain_scoped_provider_spec.rb
  • spec/specification/flag_evaluation_api_spec.rb
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.54%. Comparing base (f37850a) to head (ac6244b).
⚠️ Report is 2 commits behind head on main.

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@josecolella
josecolella marked this pull request as ready for review October 23, 2026 02:36
Copilot AI balanced review requested due to automatic review settings October 23, 2026 02:36
@josecolella
josecolella requested a review from a team as a code owner October 23, 2026 02:36
@josecolella
josecolella merged commit f3cf6b7 into main Oct 23, 2026
29 checks passed
@josecolella
josecolella deleted the feat/domain-in-provider-init branch October 23, 2026 02:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Supply the bound domain to provider initialization

3 participants