Skip to content

Commit 1ff6fd0

Browse files
fix: synchronize provider registration (#136)
Signed-off-by: Manuel Schönlaub <[email protected]> Co-authored-by: Max VelDink <[email protected]>
1 parent 51155a7 commit 1ff6fd0

File tree

4 files changed

+57
-5
lines changed

4 files changed

+57
-5
lines changed

lib/open_feature/sdk/configuration.rb

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Configuration
1616
def initialize
1717
@hooks = []
1818
@providers = {}
19+
@provider_mutex = Mutex.new
1920
end
2021

2122
def provider(domain: nil)
@@ -27,11 +28,13 @@ def provider(domain: nil)
2728
# 2. On the new provider, call `init`.
2829
# 3. Finally, set the internal provider to the new provider
2930
def set_provider(provider, domain: nil)
30-
@providers[domain].shutdown if @providers[domain].respond_to?(:shutdown)
31-
32-
provider.init if provider.respond_to?(:init)
33-
34-
@providers[domain] = provider
31+
@provider_mutex.synchronize do
32+
@providers[domain].shutdown if @providers[domain].respond_to?(:shutdown)
33+
provider.init if provider.respond_to?(:init)
34+
new_providers = @providers.dup
35+
new_providers[domain] = provider
36+
@providers = new_providers
37+
end
3538
end
3639
end
3740
end

spec/open_feature/sdk/configuration_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,21 @@
3838
expect(configuration.provider(domain: "testing")).to be(provider)
3939
end
4040
end
41+
42+
context "when the provider is set concurrently" do
43+
let(:provider) { OpenFeature::SDK::Provider::InMemoryProvider.new }
44+
it "does not not call shutdown hooks multiple times if multithreaded" do
45+
providers = (0..2).map { OpenFeature::SDK::Provider::NoOpProvider.new }
46+
providers.each { |provider| expect(provider).to receive(:init) }
47+
providers[0, 2].each { |provider| expect(provider).to receive(:shutdown) }
48+
configuration.set_provider(providers[0])
49+
50+
allow(providers[0]).to receive(:shutdown).once { sleep 0.5 }
51+
background { configuration.set_provider(providers[1]) }
52+
background { configuration.set_provider(providers[2]) }
53+
yield_to_background
54+
expect(configuration.provider).to be(providers[2])
55+
end
56+
end
4157
end
4258
end

spec/spec_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
require "debug"
1010

11+
Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
12+
1113
RSpec.configure do |config|
1214
# Enable flags like --only-failures and --next-failure
1315
config.example_status_persistence_file_path = ".rspec_status"

spec/support/background_helper.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# frozen_string_literal: true
2+
3+
module BackgroundHelper
4+
attr_writer :threads
5+
6+
private
7+
8+
def background(&)
9+
thread = Thread.new(&)
10+
thread.report_on_exception = false
11+
threads << thread
12+
thread.join(0.1)
13+
thread
14+
end
15+
16+
def threads
17+
@threads ||= []
18+
end
19+
20+
def yield_to_background
21+
threads.each(&:join)
22+
end
23+
end
24+
25+
RSpec.configure do |config|
26+
config.after do
27+
threads.each(&:kill)
28+
self.threads = []
29+
end
30+
config.include(BackgroundHelper)
31+
end

0 commit comments

Comments
 (0)