From a7498e36eeb4250ea95fdb36bdf0c8b44d3e2264 Mon Sep 17 00:00:00 2001 From: Erdi Rowlands Date: Wed, 2 Oct 2024 18:44:19 +0100 Subject: [PATCH] FFM-12088 Retry on HTTP status code 0 / add optional timeout to wait_for_initialization (#44) * FFM-12088 Retry on http code 0 * FFM-12088 Return http code if it is there * FFM-12088 Bump version * FFM-12088 Add wait_for_init timeout + sdk code * FFM-12088 Update docs * FFM-12088 Update log * FFM-12088 Change timeout param name * FFM-12088 Remove commented out code * FFM-12088 Bump minor version --- docs/further_reading.md | 44 +++++++++++++++++++ lib/ff/ruby/server/sdk/api/auth_service.rb | 3 +- lib/ff/ruby/server/sdk/api/cf_client.rb | 6 +-- lib/ff/ruby/server/sdk/api/inner_client.rb | 21 +++++++-- lib/ff/ruby/server/sdk/common/sdk_codes.rb | 12 +++++ .../server/sdk/connector/harness_connector.rb | 5 +++ lib/ff/ruby/server/sdk/version.rb | 2 +- scripts/sdk_specs.sh | 2 +- 8 files changed, 84 insertions(+), 11 deletions(-) diff --git a/docs/further_reading.md b/docs/further_reading.md index 0a67f0a..86391d1 100644 --- a/docs/further_reading.md +++ b/docs/further_reading.md @@ -24,6 +24,50 @@ client.init(apiKey, ConfigBuilder | enableStream | analytics_enabled(false) | Enable streaming mode. | true | | enableAnalytics | stream_enabled(true) | Enable analytics. Metrics data is posted every 60s | true | +## Client Initialization Options +The Harness Feature Flags SDK for Ruby provides flexible initialization strategies to accommodate various application requirements. You can choose between an asynchronous (non-blocking) or synchronous (blocking) approach to initialize the SDK. + +### Asynchronous (Non-Blocking) Initialization +The SDK can be initialized asynchronously without blocking the main thread or requiring a callback. In this case, defaults will be served until the SDK completes the initialization process. + +```ruby +client = CfClient.instance +client.init(api_key, config) + +# Will serve default until the SDK completes initialization +result = client.bool_variation("bool_flag", target, false) +``` + +### Synchronous (Blocking) Initialization + +In cases where it's critical to ensure the SDK is initialized before evaluating flags, the SDK offers a synchronous initialization method. This approach blocks the current thread until the SDK is fully initialized or the optional specified timeout (in milliseconds) period elapses. + +The synchronous method is useful for environments where feature flag decisions are needed before continuing, such as during application startup. + +You can use the `wait_for_initialization` method, optionally providing a timeout in milliseconds to prevent waiting indefinitely in case of unrecoverable isues, e.g. incorrect API key used. + +**Usage with a timeout** + +```ruby +client = CfClient.instance +client.init(api_key, config) + +client.wait_for_initialization + +result = client.bool_variation("bool_flag", target, false) +``` + +**Usage without a timeout** + +```ruby +client = CfClient.instance +client.init(api_key, config) + +client.wait_for_initialization(timeout_ms: 3000) + +result = client.bool_variation("bool_flag", target, false) +``` + ## Logging Configuration You can provide your own logger to the SDK i.e. using the moneta logger we can do this diff --git a/lib/ff/ruby/server/sdk/api/auth_service.rb b/lib/ff/ruby/server/sdk/api/auth_service.rb index b7d862c..14080f2 100644 --- a/lib/ff/ruby/server/sdk/api/auth_service.rb +++ b/lib/ff/ruby/server/sdk/api/auth_service.rb @@ -89,8 +89,9 @@ def should_retry_http_code(code) # 503 service unavailable # 504 gateway timeout # -1 OpenAPI error (timeout etc) + # 0 Un-categorised typhoeus error case code - when 408,425,429,500,502,503,504,-1 + when 408,425,429,500,502,503,504,-1, 0 return true else return false diff --git a/lib/ff/ruby/server/sdk/api/cf_client.rb b/lib/ff/ruby/server/sdk/api/cf_client.rb index 2f02f5d..2cf215c 100644 --- a/lib/ff/ruby/server/sdk/api/cf_client.rb +++ b/lib/ff/ruby/server/sdk/api/cf_client.rb @@ -50,11 +50,9 @@ def init(api_key = nil, config = nil, connector = nil) end end - def wait_for_initialization - + def wait_for_initialization(timeout_ms: nil) if @client != nil - - @client.wait_for_initialization + @client.wait_for_initialization(timeout: timeout_ms) end end diff --git a/lib/ff/ruby/server/sdk/api/inner_client.rb b/lib/ff/ruby/server/sdk/api/inner_client.rb index a4b1b27..898177d 100644 --- a/lib/ff/ruby/server/sdk/api/inner_client.rb +++ b/lib/ff/ruby/server/sdk/api/inner_client.rb @@ -231,19 +231,23 @@ def on_processor_ready(processor) @initialized = true end - def wait_for_initialization - + def wait_for_initialization(timeout: nil) synchronize do + SdkCodes::info_sdk_waiting_to_initialize(@config.logger, timeout) - @config.logger.debug "Waiting for initialization to finish" + start_time = Time.now until @initialized + # Check if a timeout is specified and has been exceeded + if timeout && (Time.now - start_time) > (timeout / 1000.0) + @config.logger.warn "The SDK has timed out waiting to initialize with supplied timeout #{timeout} ms" + handle_initialization_failure + end sleep(1) end if @failure - raise "Initialization failed" end @@ -251,8 +255,17 @@ def wait_for_initialization end end + protected + def handle_initialization_failure + @auth_service.close + @poll_processor.stop + @update_processor.stop + @metrics_processor.stop + on_auth_failed + end + def setup @repository = StorageRepository.new(@config.cache, @repository_callback, @config.store, @config.logger) diff --git a/lib/ff/ruby/server/sdk/common/sdk_codes.rb b/lib/ff/ruby/server/sdk/common/sdk_codes.rb index b5bde06..7b69744 100644 --- a/lib/ff/ruby/server/sdk/common/sdk_codes.rb +++ b/lib/ff/ruby/server/sdk/common/sdk_codes.rb @@ -14,6 +14,17 @@ def self.info_sdk_init_ok(logger) logger.info SdkCodes.sdk_err_msg(1000) end + def self.info_sdk_waiting_to_initialize(logger, timeout) + if timeout + message = "with timeout: #{timeout} ms" + else + + message = "with no timeout" + + end + logger.info SdkCodes.sdk_err_msg(1003, message) + end + def self.info_sdk_auth_ok(logger) logger.info SdkCodes.sdk_err_msg(2000) end @@ -76,6 +87,7 @@ def self.warn_bucket_by_attr_not_found(logger, attr_name, new_value) 1000 => "The SDK has successfully initialized", 1001 => "The SDK has failed to initialize due to the following authentication error:", 1002 => "The SDK has failed to initialize due to a missing or empty API key", + 1003 => "The SDK is waiting for initialzation to complete", # SDK_AUTH_2xxx 2000 => "Authenticated ok", 2001 => "Authentication failed with a non-recoverable error - defaults will be served", diff --git a/lib/ff/ruby/server/sdk/connector/harness_connector.rb b/lib/ff/ruby/server/sdk/connector/harness_connector.rb index 148ea29..c2ffe25 100644 --- a/lib/ff/ruby/server/sdk/connector/harness_connector.rb +++ b/lib/ff/ruby/server/sdk/connector/harness_connector.rb @@ -46,6 +46,11 @@ def authenticate # determine if a timeout has occurred. This is fixed in 6.3.0 but requires Ruby version to be increased to 2.7 # https://github.com/OpenAPITools/openapi-generator/releases/tag/v6.3.0 @config.logger.warn "OpenapiClient::ApiError [\n\n#{e}\n]" + + if e.code + return e.code + end + return -1 end diff --git a/lib/ff/ruby/server/sdk/version.rb b/lib/ff/ruby/server/sdk/version.rb index 8f4fa24..2cdbba2 100644 --- a/lib/ff/ruby/server/sdk/version.rb +++ b/lib/ff/ruby/server/sdk/version.rb @@ -5,7 +5,7 @@ module Ruby module Server module Sdk - VERSION = "1.3.2" + VERSION = "1.4.0" end end end diff --git a/scripts/sdk_specs.sh b/scripts/sdk_specs.sh index d213486..a687c96 100755 --- a/scripts/sdk_specs.sh +++ b/scripts/sdk_specs.sh @@ -1,4 +1,4 @@ #!/bin/bash export ff_ruby_sdk="ff-ruby-server-sdk" -export ff_ruby_sdk_version="1.3.2" +export ff_ruby_sdk_version="1.4.0"