Skip to content

Commit

Permalink
FFM-12088 Retry on HTTP status code 0 / add optional timeout to wait_…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
erdirowlands authored Oct 2, 2024
1 parent 009b477 commit a7498e3
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 11 deletions.
44 changes: 44 additions & 0 deletions docs/further_reading.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion lib/ff/ruby/server/sdk/api/auth_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 2 additions & 4 deletions lib/ff/ruby/server/sdk/api/cf_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 17 additions & 4 deletions lib/ff/ruby/server/sdk/api/inner_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -231,28 +231,41 @@ 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

@config.logger.debug "Waiting for initialization has completed"
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)
Expand Down
12 changes: 12 additions & 0 deletions lib/ff/ruby/server/sdk/common/sdk_codes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions lib/ff/ruby/server/sdk/connector/harness_connector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion lib/ff/ruby/server/sdk/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Ruby
module Server
module Sdk

VERSION = "1.3.2"
VERSION = "1.4.0"
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion scripts/sdk_specs.sh
Original file line number Diff line number Diff line change
@@ -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"

0 comments on commit a7498e3

Please sign in to comment.