Skip to content

Commit 30e37ce

Browse files
committed
Merge pull request #194 from zanker/state-tracking
Add an extra set of state tracking for Timeout.timeout edge cases
2 parents e2ecf81 + 14ff335 commit 30e37ce

File tree

3 files changed

+31
-8
lines changed

3 files changed

+31
-8
lines changed

.rubocop.yml

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Metrics/ClassLength:
55
CountComments: false
66
Max: 110
77

8+
Metrics/PerceivedComplexity:
9+
Max: 8
10+
811
Metrics/CyclomaticComplexity:
912
Max: 8 # TODO: Lower to 6
1013

lib/http/client.rb

+9-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class Client
1919
def initialize(default_options = {})
2020
@default_options = HTTP::Options.new(default_options)
2121
@connection = nil
22+
@state = :clean
2223
end
2324

2425
# Make an HTTP request
@@ -56,6 +57,8 @@ def perform(req, options)
5657
def make_request(req, options)
5758
verify_connection!(req.uri)
5859

60+
@state = :dirty
61+
5962
@connection ||= HTTP::Connection.new(req, options)
6063
@connection.send_request(req)
6164
@connection.read_headers!
@@ -69,6 +72,7 @@ def make_request(req, options)
6972
)
7073

7174
@connection.finish_response if req.verb == :head
75+
@state = :clean
7276

7377
res
7478

@@ -83,6 +87,7 @@ def make_request(req, options)
8387
def close
8488
@connection.close if @connection
8589
@connection = nil
90+
@state = :clean
8691
end
8792

8893
private
@@ -91,11 +96,14 @@ def close
9196
def verify_connection!(uri)
9297
if default_options.persistent? && base_host(uri) != default_options.persistent
9398
fail StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{base_host(uri)}"
94-
9599
# We re-create the connection object because we want to let prior requests
96100
# lazily load the body as long as possible, and this mimics prior functionality.
97101
elsif @connection && (!@connection.keep_alive? || @connection.expired?)
98102
close
103+
# If we get into a bad state (eg, Timeout.timeout ensure being killed)
104+
# close the connection to prevent potential for mixed responses.
105+
elsif @state == :dirty
106+
close
99107
end
100108
end
101109

spec/support/http_handling_shared.rb

+19-7
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,13 @@
9595
it "errors if connecting takes too long" do
9696
socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
9797

98-
fake_socket = double(:to_io => socket)
99-
expect(fake_socket).to receive(:connect_nonblock) do |*args|
98+
fake_socket_id = double(:to_io => socket)
99+
expect(fake_socket_id).to receive(:connect_nonblock) do |*args|
100100
sleep 1.25
101101
socket.connect_nonblock(*args)
102102
end
103103

104-
allow_any_instance_of(timeout_class).to receive(:socket).and_return(fake_socket)
104+
allow_any_instance_of(timeout_class).to receive(:socket).and_return(fake_socket_id)
105105

106106
expect { response }.to raise_error(HTTP::TimeoutError, /Timed out/)
107107
end
@@ -148,6 +148,18 @@
148148
expect(sockets_used.uniq.length).to eq(1)
149149
end
150150

151+
context "on a mixed state" do
152+
it "re-opens the connection" do
153+
first_socket_id = client.get("#{server.endpoint}/socket/1").body.to_s
154+
155+
client.instance_variable_set(:@state, :dirty)
156+
157+
second_socket_id = client.get("#{server.endpoint}/socket/2").body.to_s
158+
159+
expect(first_socket_id).to_not eq(second_socket_id)
160+
end
161+
end
162+
151163
context "when trying to read a stale body" do
152164
it "errors" do
153165
client.get("#{server.endpoint}/not-found")
@@ -169,8 +181,8 @@
169181

170182
context "with a socket issue" do
171183
it "transparently reopens" do
172-
first_socket = client.get("#{server.endpoint}/socket").body.to_s
173-
expect(first_socket).to_not eq("")
184+
first_socket_id = client.get("#{server.endpoint}/socket").body.to_s
185+
expect(first_socket_id).to_not eq("")
174186
# Kill off the sockets we used
175187
# rubocop:disable Style/RescueModifier
176188
DummyServer::Servlet.sockets.each do |socket|
@@ -183,8 +195,8 @@
183195
expect { client.get("#{server.endpoint}/socket").body.to_s }.to raise_error(IOError)
184196

185197
# Should succeed since we create a new socket
186-
second_socket = client.get("#{server.endpoint}/socket").body.to_s
187-
expect(second_socket).to_not eq(first_socket)
198+
second_socket_id = client.get("#{server.endpoint}/socket").body.to_s
199+
expect(second_socket_id).to_not eq(first_socket_id)
188200
end
189201
end
190202

0 commit comments

Comments
 (0)