Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix leaky file descriptors and reuse socket for persistent connections #976

Merged
merged 4 commits into from Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 7 additions & 16 deletions lib/webmock/http_lib_adapters/net_http.rb
Expand Up @@ -98,13 +98,8 @@ def request(request, body = nil, &block)
after_request.call(response)
}
if started?
if WebMock::Config.instance.net_http_connect_on_start
super_with_after_request.call
else
start_with_connect_without_finish {
super_with_after_request.call
}
end
ensure_actual_connection
super_with_after_request.call
else
start_with_connect {
super_with_after_request.call
Expand All @@ -119,26 +114,21 @@ def start_without_connect
raise IOError, 'HTTP session already opened' if @started
if block_given?
begin
@socket = Net::HTTP.socket_type.new

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@started = true
return yield(self)
ensure
do_finish
end
end
@socket = Net::HTTP.socket_type.new
@started = true
self
end


def start_with_connect_without_finish # :yield: http
if block_given?
begin
do_start
return yield(self)
end
end
do_start
self
def ensure_actual_connection
do_start if @socket.is_a?(StubSocket)
end

alias_method :start_with_connect, :start
Expand Down Expand Up @@ -258,6 +248,7 @@ def io

class StubIO
def setsockopt(*args); end
def peer_cert; end
end
end

Expand Down
53 changes: 44 additions & 9 deletions spec/acceptance/net_http/net_http_shared.rb
Expand Up @@ -26,23 +26,58 @@

it "should connect only once when connected on start", net_connect: true do
@http = Net::HTTP.new('localhost', port)
socket_id_before_request = socket_id_after_request = nil
socket_before_request = socket_after_request = nil
@http.start {|conn|
socket_id_before_request = conn.instance_variable_get(:@socket).object_id
socket_before_request = conn.instance_variable_get(:@socket)
conn.request(Net::HTTP::Get.new("/"))
socket_id_after_request = conn.instance_variable_get(:@socket).object_id
socket_after_request = conn.instance_variable_get(:@socket)
}

if !defined?(WebMock::Config) || WebMock::Config.instance.net_http_connect_on_start
expect(socket_id_before_request).not_to eq(nil.object_id)
expect(socket_id_after_request).not_to eq(nil.object_id)
expect(socket_id_after_request).to eq(socket_id_before_request)
if WebMock::Config.instance.net_http_connect_on_start
expect(socket_before_request).to be_a(Net::BufferedIO)
expect(socket_after_request).to be_a(Net::BufferedIO)
expect(socket_after_request).to be(socket_before_request)
else
expect(socket_id_before_request).to eq(nil.object_id)
expect(socket_id_after_request).not_to eq(nil.object_id)
expect(socket_before_request).to be_a(StubSocket)
expect(socket_after_request).to be_a(Net::BufferedIO)
end
end

it "should allow sending multiple requests when persisted", net_connect: true do
@http = Net::HTTP.new('example.org')
@http.start
expect(@http.get("/")).to be_a(Net::HTTPSuccess)
expect(@http.get("/")).to be_a(Net::HTTPSuccess)
expect(@http.get("/")).to be_a(Net::HTTPSuccess)
@http.finish
end

it "should not leak file descriptors", net_connect: true do
sockets = Set.new

@http = Net::HTTP.new('example.org')
@http.start
sockets << @http.instance_variable_get(:@socket)
@http.get("/")
sockets << @http.instance_variable_get(:@socket)
@http.get("/")
sockets << @http.instance_variable_get(:@socket)
@http.get("/")
sockets << @http.instance_variable_get(:@socket)
@http.finish

if WebMock::Config.instance.net_http_connect_on_start
expect(sockets.length).to eq(1)
expect(sockets.to_a[0]).to be_a(Net::BufferedIO)
else
expect(sockets.length).to eq(2)
expect(sockets.to_a[0]).to be_a(StubSocket)
expect(sockets.to_a[1]).to be_a(Net::BufferedIO)
end

expect(sockets.all?(&:closed?)).to be(true)
end

it "should pass the read_timeout value on", net_connect: true do
@http = Net::HTTP.new('localhost', port)
read_timeout = @http.read_timeout + 1
Expand Down