Skip to content

Commit 14ff335

Browse files
author
Zach Anker
committed
Add an extra set of state tracking for Timeout.timeout edge cases
1 parent 104c468 commit 14ff335

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
@@ -58,6 +59,8 @@ def perform(req, options)
5859
def make_request(req, options)
5960
verify_connection!(req.uri)
6061

62+
@state = :dirty
63+
6164
@connection ||= HTTP::Connection.new(req, options)
6265
@connection.send_request(req)
6366
@connection.read_headers!
@@ -71,6 +74,7 @@ def make_request(req, options)
7174
)
7275

7376
@connection.finish_response if req.verb == :head
77+
@state = :clean
7478

7579
res
7680

@@ -85,6 +89,7 @@ def make_request(req, options)
8589
def close
8690
@connection.close if @connection
8791
@connection = nil
92+
@state = :clean
8893
end
8994

9095
private
@@ -93,11 +98,14 @@ def close
9398
def verify_connection!(uri)
9499
if default_options.persistent? && base_host(uri) != default_options.persistent
95100
fail StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{base_host(uri)}"
96-
97101
# We re-create the connection object because we want to let prior requests
98102
# lazily load the body as long as possible, and this mimics prior functionality.
99103
elsif @connection && (!@connection.keep_alive? || @connection.expired?)
100104
close
105+
# If we get into a bad state (eg, Timeout.timeout ensure being killed)
106+
# close the connection to prevent potential for mixed responses.
107+
elsif @state == :dirty
108+
close
101109
end
102110
end
103111

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)