Skip to content

Commit 550a46f

Browse files
fix bugs and improve example
1 parent 9cf1bf1 commit 550a46f

File tree

4 files changed

+70
-46
lines changed

4 files changed

+70
-46
lines changed

examples/adaptive_with_net_http.rb

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,44 @@
1414

1515
# Configure a mock HTTP server endpoint
1616
# In real usage, this would be your actual service endpoint
17-
TEST_HOST = "httpbin.org"
18-
TEST_PORT = 443
17+
TEST_HOST = "localhost"
18+
TEST_PORT = 80
1919

2020
# Configure Semian for the HTTP endpoint with adaptive circuit breaker
2121
puts "\nConfiguring Semian with adaptive circuit breaker..."
2222

2323
# The Net::HTTP adapter will automatically use this configuration
2424
# when making requests to the specified host
25-
Semian.register(
26-
"httpbin_service",
27-
adaptive_circuit_breaker: true, # Enable adaptive circuit breaker
28-
bulkhead: true,
29-
tickets: 5,
30-
timeout: 1,
31-
)
25+
SEMIAN_PARAMETERS = {
26+
adaptive_circuit_breaker: true,
27+
open_circuit_server_errors: true,
28+
}.freeze
29+
30+
Semian::NetHTTP.semian_configuration = proc do |host, port|
31+
if host == "localhost" && port == 80
32+
SEMIAN_PARAMETERS.merge(name: "localhost_80")
33+
end
34+
end
3235

3336
# Helper method to make HTTP requests
3437
def make_request(path = "/status/200")
35-
uri = URI("https://#{TEST_HOST}#{path}")
38+
uri = URI("http://#{TEST_HOST}#{path}")
3639
http = Net::HTTP.new(uri.host, uri.port)
37-
http.use_ssl = true
3840
http.read_timeout = 2
3941
http.open_timeout = 2
4042

41-
# Configure semian for this connection
42-
# This would typically be done in your HTTP client configuration
43-
http.singleton_class.class_eval do
44-
def semian_identifier
45-
"httpbin_service"
46-
end
47-
48-
def semian_options
49-
# Return the resource name to use the registered configuration
50-
{ name: "httpbin_service" }
51-
end
52-
end
43+
# # Configure semian for this connection
44+
# # This would typically be done in your HTTP client configuration
45+
# http.singleton_class.class_eval do
46+
# def semian_identifier
47+
# "httpbin_service"
48+
# end
49+
50+
# def semian_options
51+
# # Return the resource name to use the registered configuration
52+
# { name: "httpbin_service" }
53+
# end
54+
# end
5355

5456
request = Net::HTTP::Get.new(uri)
5557
response = http.request(request)
@@ -68,6 +70,7 @@ def semian_options
6870
puts "Request #{i + 1}: Success (HTTP #{code})"
6971
rescue => e
7072
puts "Request #{i + 1}: Error - #{e.class}: #{e.message}"
73+
puts e.backtrace
7174
end
7275
sleep(0.5)
7376
end

lib/semian/adapter.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def acquire_semian_resource(scope:, adapter:, &block)
4545
end
4646
rescue ::Semian::OpenCircuitError => error
4747
last_error = semian_resource.circuit_breaker.last_error
48-
message = "#{error.message} caused by #{last_error.message}"
48+
message = "#{error.message} caused by #{last_error&.message}"
4949
last_error = nil unless last_error.is_a?(Exception) # Net::HTTPServerError is not an exception
5050
raise self.class::CircuitOpenError.new(semian_identifier, message), cause: last_error
5151
rescue ::Semian::BaseError => error

lib/semian/adaptive_circuit_breaker.rb

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
module Semian
66
# Adaptive Circuit Breaker that uses PID controller for dynamic rejection
77
class AdaptiveCircuitBreaker
8-
attr_reader :name, :pid_controller, :ping_thread, :update_thread
8+
attr_reader :name, :pid_controller, :ping_thread, :update_thread, :last_error
99

1010
def initialize(name:, kp: 1.0, ki: 0.1, kd: 0.01,
1111
window_size: 10, history_duration: 3600,
@@ -17,6 +17,7 @@ def initialize(name:, kp: 1.0, ki: 0.1, kd: 0.01,
1717
@enable_background_ping = enable_background_ping
1818
@resource = nil
1919
@stopped = false
20+
@last_error = nil
2021

2122
# Create PID controller (thread-safe by default)
2223
@pid_controller = if thread_safe
@@ -62,6 +63,7 @@ def acquire(resource = nil, &block)
6263
result
6364
rescue => error
6465
@pid_controller.record_request(:error)
66+
@last_error = error # Store the error for reporting
6567
raise error
6668
end
6769
end
@@ -70,6 +72,7 @@ def acquire(resource = nil, &block)
7072
def reset
7173
@pid_controller.reset
7274
@resource = nil
75+
@last_error = nil
7376
end
7477

7578
# Stop the background threads (called by destroy)
@@ -107,6 +110,27 @@ def half_open?
107110
!open? && !closed?
108111
end
109112

113+
# Mark a request as failed (for compatibility with ProtectedResource)
114+
def mark_failed(error)
115+
@last_error = error
116+
@pid_controller.record_request(:error)
117+
end
118+
119+
# Mark a request as successful (for compatibility with ProtectedResource)
120+
def mark_success
121+
@pid_controller.record_request(:success)
122+
end
123+
124+
# Check if requests are allowed (for compatibility with ProtectedResource)
125+
def request_allowed?
126+
!@pid_controller.should_reject?
127+
end
128+
129+
# Check if the circuit breaker is in use (for compatibility with ProtectedResource)
130+
def in_use?
131+
true
132+
end
133+
110134
private
111135

112136
def start_ping_thread

lib/semian/net_http.rb

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -127,28 +127,25 @@ def with_resource_timeout(timeout)
127127
def unprotected_ping
128128
# Perform a simple HEAD request to check connectivity
129129
# First ensure we're connected
130-
return false unless started?
131130

132-
begin
133-
# Create a simple HEAD request
134-
req = Net::HTTP::Head.new("/")
135-
136-
# Temporarily save semian state and disable it
137-
saved_semian_enabled = @semian_enabled
138-
@semian_enabled = false
139-
140-
# Make the request directly without going through Semian
141-
# We just need to verify we can reach the server
142-
request(req)
143-
144-
# Any response (even error responses) means the server is reachable
145-
true
146-
rescue => e
147-
Semian.logger&.debug("[net_http] Unprotected ping failed: #{e.message}")
148-
false
149-
ensure
150-
@semian_enabled = saved_semian_enabled
151-
end
131+
# Create a simple HEAD request
132+
req = Net::HTTP::Head.new("/")
133+
134+
# Temporarily save semian state and disable it
135+
saved_semian_enabled = @semian_enabled
136+
@semian_enabled = false
137+
138+
# Make the request directly without going through Semian
139+
# We just need to verify we can reach the server
140+
request(req)
141+
142+
# Any response (even error responses) means the server is reachable
143+
true
144+
rescue => e
145+
Semian.logger&.debug("[net_http] Unprotected ping failed: #{e.message}")
146+
false
147+
ensure
148+
@semian_enabled = saved_semian_enabled
152149
end
153150

154151
private

0 commit comments

Comments
 (0)