diff --git a/.rubocop.yml b/.rubocop.yml index 842354327..35f4e17ff 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -42,6 +42,8 @@ Metrics/MethodLength: Metrics/ClassLength: Max: 500 + Exclude: + - test/**/* Metrics/AbcSize: Max: 50 diff --git a/README.md b/README.md index 7987d2af7..84a674d7d 100644 --- a/README.md +++ b/README.md @@ -431,7 +431,7 @@ There are three configuration parameters for circuit breakers in Semian: again. * **success_threshold**. The amount of successes on the circuit until closing it again, that is to start accepting all requests to the circuit. -* **half_open_resource_timeout**. Timeout for the resource in seconds when the circuit is half-open (only supported for MySQL). +* **half_open_resource_timeout**. Timeout for the resource in seconds when the circuit is half-open (only supported for MySQL and Net::HTTP). ### Bulkheading diff --git a/lib/semian/circuit_breaker.rb b/lib/semian/circuit_breaker.rb index 67ebddd66..8f9a5b430 100644 --- a/lib/semian/circuit_breaker.rb +++ b/lib/semian/circuit_breaker.rb @@ -125,15 +125,14 @@ def disabled? end def maybe_with_half_open_resource_timeout(resource, &block) - result = nil - - if half_open? && @half_open_resource_timeout && resource.respond_to?(:with_resource_timeout) - resource.with_resource_timeout(@half_open_resource_timeout) do - result = block.call + result = + if half_open? && @half_open_resource_timeout && resource.respond_to?(:with_resource_timeout) + resource.with_resource_timeout(@half_open_resource_timeout) do + block.call + end + else + block.call end - else - result = block.call - end result end diff --git a/lib/semian/net_http.rb b/lib/semian/net_http.rb index 3c5f91b21..9be768863 100644 --- a/lib/semian/net_http.rb +++ b/lib/semian/net_http.rb @@ -90,6 +90,19 @@ def transport_request(*) end end + def with_resource_timeout(timeout) + prev_read_timeout = read_timeout + prev_open_timeout = open_timeout + begin + self.read_timeout = timeout + self.open_timeout = timeout + yield + ensure + self.read_timeout = prev_read_timeout + self.open_timeout = prev_open_timeout + end + end + private def handle_error_responses(result) diff --git a/test/net_http_test.rb b/test/net_http_test.rb index 949d741e6..daba5b19c 100644 --- a/test/net_http_test.rb +++ b/test/net_http_test.rb @@ -42,6 +42,42 @@ def test_semian_identifier end end + def test_changes_timeout_when_half_open_and_configured + http = Net::HTTP.new(SemianConfig['toxiproxy_upstream_host'], SemianConfig['http_toxiproxy_port']) + expected_read_timeout = http.read_timeout + expected_open_timeout = http.open_timeout + options = proc do |host, port| + { + tickets: 3, + success_threshold: 2, + error_threshold: 2, + error_timeout: 10, + open_circuit_server_errors: true, + name: "#{host}_#{port}", + half_open_resource_timeout: 1, + } + end + + with_semian_configuration(options) do + with_server do + Toxiproxy['semian_test_net_http'].downstream(:latency, latency: 2000).apply do + http.get('/200') + end + + half_open_cicuit! + + Toxiproxy['semian_test_net_http'].downstream(:latency, latency: 2000).apply do + assert_raises Net::ReadTimeout do + http.get('/200') + end + end + end + end + + assert_equal expected_read_timeout, http.read_timeout + assert_equal expected_open_timeout, http.open_timeout + end + def test_trigger_open with_semian_configuration do with_server do @@ -396,6 +432,12 @@ def test_persistent_state_after_server_restart private + def half_open_cicuit!(backwards_time_travel = 10) + Timecop.travel(Time.now - backwards_time_travel) do + open_circuit! + end + end + def with_semian_configuration(options = DEFAULT_SEMIAN_CONFIGURATION) orig_semian_options = Semian::NetHTTP.semian_configuration Semian::NetHTTP.instance_variable_set(:@semian_configuration, nil)