diff --git a/.gitignore b/.gitignore index b04a8c84..6f2b9076 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ # rspec failure tracking .rspec_status +vendor/bundle diff --git a/README.md b/README.md index 6ce7d6d0..eec203c1 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ object = client.fetch_object_value(flag_key: 'object_value', default_value: JSON | ❌ | [Logging](#logging) | Integrate with popular logging packages. | | ✅ | [Domains](#domains) | Logically bind clients with providers. | | ❌ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | -| ⚠️ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | +| ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | | ❌ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread) | | ⚠️ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | @@ -206,11 +206,33 @@ Please refer to the documentation of the provider you're using to see what event ### Shutdown -Coming Soon! [Issue available](https://github.com/open-feature/ruby-sdk/issues/149) to be worked on. - - +``` ### Transaction Context Propagation diff --git a/lib/open_feature/sdk/api.rb b/lib/open_feature/sdk/api.rb index 831648b4..0cad3708 100644 --- a/lib/open_feature/sdk/api.rb +++ b/lib/open_feature/sdk/api.rb @@ -32,7 +32,7 @@ class API include Singleton # Satisfies Flag Evaluation API Requirement 1.1.1 extend Forwardable - def_delegators :configuration, :provider, :set_provider, :hooks, :evaluation_context + def_delegators :configuration, :provider, :set_provider, :hooks, :evaluation_context, :shutdown def configuration @configuration ||= Configuration.new diff --git a/lib/open_feature/sdk/configuration.rb b/lib/open_feature/sdk/configuration.rb index 321baa24..d43b7c7b 100644 --- a/lib/open_feature/sdk/configuration.rb +++ b/lib/open_feature/sdk/configuration.rb @@ -36,6 +36,16 @@ def set_provider(provider, domain: nil) @providers = new_providers end end + + # Shutdown all registered providers and clear the configuration + def shutdown + @provider_mutex.synchronize do + @providers.each_value do |provider| + provider.shutdown if provider.respond_to?(:shutdown) + end + @providers.clear + end + end end end end diff --git a/spec/open_feature/sdk/configuration_spec.rb b/spec/open_feature/sdk/configuration_spec.rb index 73d1464e..a14faa08 100644 --- a/spec/open_feature/sdk/configuration_spec.rb +++ b/spec/open_feature/sdk/configuration_spec.rb @@ -55,4 +55,43 @@ end end end + + describe "#shutdown" do + context "when providers have shutdown methods" do + let(:provider1) { OpenFeature::SDK::Provider::InMemoryProvider.new } + let(:provider2) { OpenFeature::SDK::Provider::InMemoryProvider.new } + + it "calls shutdown on all providers and clears them" do + configuration.set_provider(provider1) + configuration.set_provider(provider2, domain: "testing") + + expect(provider1).to receive(:shutdown) + expect(provider2).to receive(:shutdown) + + configuration.shutdown + + # Verify providers are cleared + expect(configuration.provider).to be_nil + expect(configuration.provider(domain: "testing")).to be_nil + end + end + + context "when providers do not have shutdown methods" do + it "does not raise errors for providers without shutdown and clears them" do + provider_without_shutdown = OpenFeature::SDK::Provider::NoOpProvider.new + configuration.set_provider(provider_without_shutdown) + + expect { configuration.shutdown }.not_to raise_error + + # Verify providers are cleared + expect(configuration.provider).to be_nil + end + end + + context "when no providers are set" do + it "does not raise errors" do + expect { configuration.shutdown }.not_to raise_error + end + end + end end diff --git a/spec/specification/flag_evaluation_api_spec.rb b/spec/specification/flag_evaluation_api_spec.rb index 2ba5a9b8..6fc2c27c 100644 --- a/spec/specification/flag_evaluation_api_spec.rb +++ b/spec/specification/flag_evaluation_api_spec.rb @@ -127,6 +127,35 @@ end.not_to raise_error end end + + context "Shutdown functionality" do + specify "The API must provide a shutdown function to gracefully clean up all providers" do + provider1 = OpenFeature::SDK::Provider::InMemoryProvider.new + provider2 = OpenFeature::SDK::Provider::InMemoryProvider.new + + OpenFeature::SDK.set_provider(provider1) + OpenFeature::SDK.set_provider(provider2, domain: "testing") + + expect(provider1).to receive(:shutdown) + expect(provider2).to receive(:shutdown) + + expect { OpenFeature::SDK.shutdown }.not_to raise_error + + # Verify providers are cleared after shutdown + expect(OpenFeature::SDK.provider).to be_nil + expect(OpenFeature::SDK.provider(domain: "testing")).to be_nil + end + + specify "Shutdown does not raise errors when providers don't have shutdown methods" do + provider_without_shutdown = OpenFeature::SDK::Provider::NoOpProvider.new + OpenFeature::SDK.set_provider(provider_without_shutdown) + + expect { OpenFeature::SDK.shutdown }.not_to raise_error + + # Verify providers are cleared after shutdown + expect(OpenFeature::SDK.provider).to be_nil + end + end end context "1.2 - Client Usage" do