Skip to content

Commit f30aa19

Browse files
committed
feat: Add setProviderAndWait method for blocking provider initialization
This implements the OpenFeature specification requirement for setProviderAndWait, providing a blocking method to register providers and wait for initialization. Changes: - Add ProviderInitializationError exception class with provider and original_error context - Implement set_provider_and_wait method in Configuration class with timeout support - Add delegation in API class to expose set_provider_and_wait method - Provide SDK-level access through method_missing delegation - Add comprehensive test coverage for all scenarios including timeouts, failures, and concurrency - Update README.md with usage examples and documentation The method ensures provider readiness before returning and handles initialization failures gracefully with detailed error information. Signed-off-by: Leo Romanovsky <[email protected]>
1 parent 2f1e327 commit f30aa19

File tree

7 files changed

+438
-27
lines changed

7 files changed

+438
-27
lines changed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,42 @@ OpenFeature::SDK.configure do |config|
130130
end
131131
```
132132

133+
#### Blocking Provider Registration
134+
135+
If you need to ensure that a provider is fully initialized before continuing, you can use `set_provider_and_wait`:
136+
137+
```ruby
138+
# Using the SDK directly
139+
begin
140+
OpenFeature::SDK.set_provider_and_wait(my_provider)
141+
puts "Provider is ready!"
142+
rescue OpenFeature::SDK::ProviderInitializationError => e
143+
puts "Provider failed to initialize: #{e.message}"
144+
puts "Original error: #{e.original_error}"
145+
end
146+
147+
# With custom timeout (default is 30 seconds)
148+
OpenFeature::SDK.set_provider_and_wait(my_provider, timeout: 60)
149+
150+
# Domain-specific provider
151+
OpenFeature::SDK.set_provider_and_wait(my_provider, domain: "feature-flags")
152+
153+
# Via configuration block
154+
OpenFeature::SDK.configure do |config|
155+
begin
156+
config.set_provider_and_wait(my_provider)
157+
rescue OpenFeature::SDK::ProviderInitializationError => e
158+
# Handle initialization failure
159+
end
160+
end
161+
```
162+
163+
The `set_provider_and_wait` method:
164+
- Waits for the provider's `init` method to complete successfully
165+
- Raises `ProviderInitializationError` if initialization fails or times out
166+
- Provides access to the original error and provider instance for debugging
167+
- Uses the same thread-safe provider switching as `set_provider`
168+
133169
In some situations, it may be beneficial to register multiple providers in the same application.
134170
This is possible using [domains](#domains), which is covered in more detail below.
135171

lib/open_feature/sdk/api.rb

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# frozen_string_literal: true
22

3-
require "forwardable"
4-
require "singleton"
3+
require 'forwardable'
4+
require 'singleton'
55

6-
require_relative "configuration"
7-
require_relative "evaluation_context"
8-
require_relative "evaluation_context_builder"
9-
require_relative "evaluation_details"
10-
require_relative "client_metadata"
11-
require_relative "client"
12-
require_relative "provider"
6+
require_relative 'configuration'
7+
require_relative 'evaluation_context'
8+
require_relative 'evaluation_context_builder'
9+
require_relative 'evaluation_details'
10+
require_relative 'client_metadata'
11+
require_relative 'client'
12+
require_relative 'provider'
1313

1414
module OpenFeature
1515
module SDK
@@ -32,7 +32,7 @@ class API
3232
include Singleton # Satisfies Flag Evaluation API Requirement 1.1.1
3333
extend Forwardable
3434

35-
def_delegators :configuration, :provider, :set_provider, :hooks, :evaluation_context
35+
def_delegators :configuration, :provider, :set_provider, :set_provider_and_wait, :hooks, :evaluation_context
3636

3737
def configuration
3838
@configuration ||= Configuration.new
@@ -48,7 +48,7 @@ def build_client(domain: nil, evaluation_context: nil)
4848
active_provider = provider(domain:).nil? ? Provider::NoOpProvider.new : provider(domain:)
4949

5050
Client.new(provider: active_provider, domain:, evaluation_context:)
51-
rescue
51+
rescue StandardError
5252
Client.new(provider: Provider::NoOpProvider.new, evaluation_context:)
5353
end
5454
end

lib/open_feature/sdk/configuration.rb

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# frozen_string_literal: true
22

3-
require_relative "api"
3+
require 'timeout'
4+
require_relative 'api'
5+
require_relative 'provider_initialization_error'
46

57
module OpenFeature
68
module SDK
@@ -36,6 +38,52 @@ def set_provider(provider, domain: nil)
3638
@providers = new_providers
3739
end
3840
end
41+
42+
# Sets a provider and waits for the initialization to complete or fail.
43+
# This method ensures the provider is ready (or in error state) before returning.
44+
#
45+
# @param provider [Object] the provider to set
46+
# @param domain [String, nil] the domain for the provider (optional)
47+
# @param timeout [Integer] maximum time to wait for initialization in seconds (default: 30)
48+
# @raise [ProviderInitializationError] if the provider fails to initialize or times out
49+
def set_provider_and_wait(provider, domain: nil, timeout: 30)
50+
@provider_mutex.synchronize do
51+
old_provider = @providers[domain]
52+
53+
# Shutdown old provider (ignore errors)
54+
begin
55+
old_provider.shutdown if old_provider.respond_to?(:shutdown)
56+
rescue StandardError
57+
# Ignore shutdown errors and continue with provider initialization
58+
end
59+
60+
begin
61+
# Initialize new provider with timeout
62+
if provider.respond_to?(:init)
63+
Timeout.timeout(timeout) do
64+
provider.init
65+
end
66+
end
67+
68+
# Set the new provider
69+
new_providers = @providers.dup
70+
new_providers[domain] = provider
71+
@providers = new_providers
72+
rescue Timeout::Error => e
73+
raise ProviderInitializationError.new(
74+
"Provider initialization timed out after #{timeout} seconds",
75+
provider:,
76+
original_error: e
77+
)
78+
rescue StandardError => e
79+
raise ProviderInitializationError.new(
80+
"Provider initialization failed: #{e.message}",
81+
provider:,
82+
original_error: e
83+
)
84+
end
85+
end
86+
end
3987
end
4088
end
4189
end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# frozen_string_literal: true
2+
3+
module OpenFeature
4+
module SDK
5+
# Exception raised when a provider fails to initialize during setProviderAndWait
6+
#
7+
# This exception provides access to both the original error that caused the
8+
# initialization failure and the provider instance that failed to initialize.
9+
class ProviderInitializationError < StandardError
10+
# @return [Object] the provider that failed to initialize
11+
attr_reader :provider
12+
13+
# @return [Exception] the original error that caused the initialization failure
14+
attr_reader :original_error
15+
16+
# @param message [String] the error message
17+
# @param provider [Object] the provider that failed to initialize
18+
# @param original_error [Exception] the original error that caused the failure
19+
def initialize(message, provider: nil, original_error: nil)
20+
super(message)
21+
@provider = provider
22+
@original_error = original_error
23+
end
24+
end
25+
end
26+
end

0 commit comments

Comments
 (0)