Skip to content

Commit

Permalink
FFM-12192 Patch Four: Replace ConcurrentMap + Other Concurrency Chang…
Browse files Browse the repository at this point in the history
…es (#51)
  • Loading branch information
erdirowlands authored Nov 18, 2024
1 parent d165c3e commit c5053ce
Show file tree
Hide file tree
Showing 9 changed files with 488 additions and 180 deletions.
2 changes: 1 addition & 1 deletion ff-ruby-server-sdk.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Gem::Specification.new do |spec|

spec.add_dependency "concurrent-ruby", "~> 1.1"

spec.add_dependency "murmurhash3", "0.1.6"
spec.add_dependency "murmurhash3", "0.1.7"

spec.add_dependency "typhoeus", '~> 1.0', '>= 1.0.1'
end
15 changes: 15 additions & 0 deletions lib/ff/ruby/server/sdk/api/evaluator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,20 @@ def evaluate(identifier, target, expected, callback)

flag = @repository.get_flag(identifier)

# Check if flag exists
if flag.nil?
# Log a warning if the flag is not found
@logger.warn "Flag not found for identifier '#{identifier}'. Serving default variation."
return nil
end

# Check if the flag's kind matches the expected type
unless flag.kind == expected
@logger.warn "Flag kind mismatch: expected '#{expected}', but got '#{flag.kind}' for identifier '#{identifier}'. Serving default variation."
return nil
end

# Proceed with prerequisite check if flag type is as expected
if flag != nil && flag.kind == expected
unless flag.prerequisites.empty?
pre_req = check_pre_requisite(flag, target)
Expand All @@ -109,6 +123,7 @@ def evaluate(identifier, target, expected, callback)
end
end

# Returning nil will indicate to callers to serve the default variation
nil
end

Expand Down
104 changes: 62 additions & 42 deletions lib/ff/ruby/server/sdk/api/inner_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def initialize(api_key = nil, config = nil, connector = nil)

@connector = connector
end
@condition = ConditionVariable.new

@closing = false
@failure = false
Expand All @@ -66,22 +67,34 @@ def initialize(api_key = nil, config = nil, connector = nil)
end

def bool_variation(identifier, target, default_value)

unless @initialized
log_sdk_not_initialized_warning(identifier, default_value)
return default_value
end
@evaluator.bool_variation(identifier, target, default_value, @evaluator_callback)
end

def string_variation(identifier, target, default_value)

unless @initialized
log_sdk_not_initialized_warning(identifier, default_value)
return default_value
end
@evaluator.string_variation(identifier, target, default_value, @evaluator_callback)
end

def number_variation(identifier, target, default_value)

unless @initialized
log_sdk_not_initialized_warning(identifier, default_value)
return default_value
end
@evaluator.number_variation(identifier, target, default_value, @evaluator_callback)
end

def json_variation(identifier, target, default_value)

unless @initialized
log_sdk_not_initialized_warning(identifier, default_value)
return default_value
end
@evaluator.json_variation(identifier, target, default_value, @evaluator_callback)
end

Expand Down Expand Up @@ -109,6 +122,7 @@ def on_auth_success
def on_auth_failed
SdkCodes::warn_auth_failed_srv_defaults @config.logger
@initialized = true
@condition.signal
end

def close
Expand Down Expand Up @@ -195,67 +209,74 @@ def on_metrics_processor_ready
end

def on_processor_ready(processor)
@my_mutex.synchronize do

if @closing
if @closing

return
end
return
end

if processor == @poll_processor
if processor == @poll_processor

@poller_ready = true
@config.logger.debug "PollingProcessor ready"
end
@poller_ready = true
@config.logger.debug "PollingProcessor ready"
end

if processor == @update_processor
if processor == @update_processor

@stream_ready = true
@config.logger.debug "Updater ready"
end
@stream_ready = true
@config.logger.debug "Updater ready"
end

if processor == @metrics_processor
if processor == @metrics_processor

@metrics_ready = true
@config.logger.debug "Metrics ready"
end
@metrics_ready = true
@config.logger.debug "Metrics ready"
end

if (@config.stream_enabled && !@stream_ready) ||
(@config.analytics_enabled && !@metrics_ready) ||
!@poller_ready
if (@config.stream_enabled && !@stream_ready) ||
(@config.analytics_enabled && !@metrics_ready) ||
!@poller_ready

return
end
return
end

SdkCodes.info_sdk_init_ok @config.logger
SdkCodes.info_sdk_init_ok @config.logger

@initialized = true
@condition.signal
@initialized = true
end
end

def wait_for_initialization(timeout: nil)
synchronize do
SdkCodes::info_sdk_waiting_to_initialize(@config.logger, timeout)
SdkCodes::info_sdk_waiting_to_initialize(@config.logger, timeout)
return if @initialized

@my_mutex.synchronize do
start_time = Time.now
remaining = timeout ? timeout / 1000.0 : nil # Convert timeout to seconds

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
# Break if timeout has elapsed
if remaining && remaining <= 0
@config.logger.warn "The SDK has timed out waiting to initialize with supplied timeout #{timeout} ms. The SDK will continue to initialize in the background. Default variations will be served until the SDK initializes."
break
end
# Wait for the signal or timeout
@condition.wait(@my_mutex, remaining)

if @failure
raise "Initialization failed"
# Recalculate the remaining time after the wait
if timeout
elapsed = Time.now - start_time
remaining = (timeout / 1000.0) - elapsed
end
end

@config.logger.debug "Waiting for initialization has completed"
@config.logger.debug "Waiting for initialization has completed" if @initialized
end
end


protected

def handle_initialization_failure
Expand Down Expand Up @@ -316,9 +337,8 @@ def setup

private

def synchronize(&block)

@my_mutex.synchronize(&block)
def log_sdk_not_initialized_warning(identifier, default_value)
@config.logger.warn "SDKCODE:6001: SDK is not initialized; serving default variation for bool variation: identifier=#{identifier}, default=#{default_value}"
end

end
17 changes: 16 additions & 1 deletion lib/ff/ruby/server/sdk/api/metrics_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ def eql?(other)
# project sizes. Issue being tracked in FFM-12192, and once resolved, can feasibly remove
# these checks in a future release.
unless other.is_a?(MetricsEvent)
@logger.warn("Warning: Attempted to compare MetricsEvent with #{other.class.name}" )
# We should always have a logger available except when we've deep cloned this class. We don't do any
# equality check on clones in metrics code anyway, so this is just a safety check.
if @logger
@logger.warn("Warning: Attempted to compare MetricsEvent with #{other.class.name}")
end
return false
end

Expand All @@ -35,4 +39,15 @@ def hash
end


# Exclude logger from serialization
def marshal_dump
[@feature_config, @target, @variation]
end

def marshal_load(array)
@feature_config, @target, @variation = array
@logger = nil
end


end
Loading

0 comments on commit c5053ce

Please sign in to comment.