Skip to content

Commit

Permalink
Support non-deterministic resource configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
pjambet committed Apr 27, 2023
1 parent c8a1ee0 commit bc42b7b
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 10 deletions.
20 changes: 15 additions & 5 deletions lib/semian/adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,25 @@ def semian_identifier
end

def semian_resource
@semian_resource ||= case semian_options
return @semian_resource if @semian_resource

case semian_options
when false
UnprotectedResource.new(semian_identifier)
@semian_resource = UnprotectedResource.new(semian_identifier)
when nil
Semian.logger.info("Semian is not configured for #{self.class.name}: #{semian_identifier}")
UnprotectedResource.new(semian_identifier)
@semian_resource = UnprotectedResource.new(semian_identifier)
else
options = semian_options.dup
options.delete(:name)
options[:consumer] = self
options[:exceptions] ||= []
options[:exceptions] += resource_exceptions
::Semian.retrieve_or_register(semian_identifier, **options)
resource = ::Semian.retrieve_or_register(semian_identifier, **options)

@semian_resource = resource unless options.fetch(:dynamic, false)

resource
end
end

Expand Down Expand Up @@ -53,7 +59,11 @@ def semian_options
return @semian_options if defined? @semian_options

options = raw_semian_options
@semian_options = options && options.map { |k, v| [k.to_sym, v] }.to_h

symbolized_options = options && options.transform_keys(&:to_sym) # rubocop:disable Style/SafeNavigation
symbolized_options.tap do
@semian_options = symbolized_options if !symbolized_options || !symbolized_options.fetch(:dynamic, false)
end
end

def raw_semian_options
Expand Down
32 changes: 27 additions & 5 deletions lib/semian/net_http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,20 @@ def disabled?
end

def connect
return super if disabled?
with_cleared_dynamic_options do
return super if disabled?

acquire_semian_resource(adapter: :http, scope: :connection) { super }
acquire_semian_resource(adapter: :http, scope: :connection) { super }
end
end

def transport_request(*)
return super if disabled?
with_cleared_dynamic_options do
return super if disabled?

acquire_semian_resource(adapter: :http, scope: :query) do
handle_error_responses(super)
acquire_semian_resource(adapter: :http, scope: :query) do
handle_error_responses(super)
end
end
end

Expand All @@ -125,6 +129,24 @@ def handle_error_responses(result)
end
result
end

def with_cleared_dynamic_options
unless @resource_acquisition_in_progress
@resource_acquisition_in_progress = true
resource_acquisition_started = true
end

yield
ensure
if resource_acquisition_started
if @raw_semian_options&.fetch(:dynamic, false)
# Clear @raw_semian_options if the resource was flagged as dynamic.
@raw_semian_options = nil
end

@resource_acquisition_in_progress = false
end
end
end
end

Expand Down
9 changes: 9 additions & 0 deletions test/adapter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ def test_consumer_registration_does_not_prevent_gc
end
end

def test_does_not_memoize_dynamic_options
dynamic_client = Semian::DynamicAdapterTestClient.new(quota: 0.5)

refute_nil(dynamic_client.semian_resource)
assert_equal(4, dynamic_client.raw_semian_options[:success_threshold])
assert_equal(5, dynamic_client.raw_semian_options[:success_threshold])
assert_nil(dynamic_client.instance_variable_get("@semian_options"))
end

class MyAdapterError < StandardError
include Semian::AdapterError
end
Expand Down
29 changes: 29 additions & 0 deletions test/adapters/net_http_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def setup
destroy_all_semian_resources
end

def teardown
Thread.current[:sub_resource_name] = nil
end

def test_semian_identifier
with_server do
with_semian_configuration do
Expand Down Expand Up @@ -484,6 +488,31 @@ def test_semian_disabled_env
ENV.delete("SEMIAN_DISABLED")
end

def test_dynamic_config
options = proc do |host, port|
DEFAULT_SEMIAN_OPTIONS.merge(
dynamic: true,
name: "#{host}_#{port}_#{Thread.current[:sub_resource_name]}",
)
end
host = SemianConfig["toxiproxy_upstream_host"]
port = SemianConfig["http_toxiproxy_port"]
http = Net::HTTP.new(host, port)
Thread.current[:sub_resource_name] = "service_a"

with_semian_configuration(options) do
with_server do
http.get("/200")

Thread.current[:sub_resource_name] = "service_b"
http.get("/200")

assert_includes(Semian.resources.keys, "nethttp_#{host}_#{port}_service_a")
assert_includes(Semian.resources.keys, "nethttp_#{host}_#{port}_service_b")
end
end
end

private

def half_open_cicuit!(backwards_time_travel = 10)
Expand Down
34 changes: 34 additions & 0 deletions test/helpers/adapter_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,28 @@ def resource_exceptions
end
end

module DynamicAdapterTest
include Semian::Adapter

def semian_identifier
:dynamic_semian_adapter_test
end

def raw_semian_options
{
bulkhead: false,
error_threshold: 1,
error_timeout: 1,
dynamic: true,
success_threshold: @current_success_threshold += 1,
}
end

def resource_exceptions
[]
end
end

class AdapterTestClient
include AdapterTest

Expand All @@ -34,4 +56,16 @@ def ==(other)
inspect == other.inspect
end
end

class DynamicAdapterTestClient
include DynamicAdapterTest

def initialize(**args)
@current_success_threshold = 1
end

def ==(other)
inspect == other.inspect
end
end
end

0 comments on commit bc42b7b

Please sign in to comment.