HoneyryderChuck / httpx

(Mirror) An HTTP client library for ruby
https://gitlab.com/os85/httpx
Apache License 2.0
187 stars 10 forks source link

Per-request timeouts only respected on first request when using Faraday adapter #54

Closed myronmarston closed 3 months ago

myronmarston commented 3 months ago

When using the Faraday adapter, I've observed that per-request timeouts do not appear to work correctly. Whatever timeout value is set on the first request of the faraday connection gets re-used for all subsequent requests.

Here's a script that reproduces this bug:

require "bundler/inline"

gemfile do
  gem "rackup", "2.1.0"
  gem "httpx", "1.2.5"
  gem "faraday", "2.9.0"
end

port = 9876
require "rackup"

# Boot the server in a child process
pid = fork do
  $stdout.reopen("/dev/null", "w")
  $stderr.reopen("/dev/null", "w")

  Rackup::Server.start(Port: port, app: ->(_env) {
    sleep 3
    [200, {}, ["Hello, World!"]]
  })
end

at_exit do
  # Kill the child process on exit.
  ::Process.kill(:TERM, pid)
  ::Process.wait(pid)
end

sleep 1 # give the server a bit of time to boot

require "httpx/adapters/faraday"

conn = Faraday.new do |f|
  f.adapter ENV.fetch("FARADAY_ADAPTER").to_sym
end

ENV.fetch("TIMEOUTS").split(",").map(&:to_i).each do |timeout|
  begin
    response = conn.get("http://localhost:#{port}/") do |request|
      request.options.timeout = timeout
    end
  rescue Faraday::TimeoutError => error
    puts "Got Faraday::TimeoutError: #{error}"
  else
    puts "Did not timeout. Got response: #{response.body}"
  end
end

When I run it using the net_http adapter, you can see that the timeout set on each request is honored:

$ FARADAY_ADAPTER=net_http TIMEOUTS=10,1 ruby httpx_bug.rb
Did not timeout. Got response: Hello, World!
Got Faraday::TimeoutError: Net::ReadTimeout with #<TCPSocket:(closed)>
$ FARADAY_ADAPTER=net_http TIMEOUTS=1,10 ruby httpx_bug.rb
Got Faraday::TimeoutError: Net::ReadTimeout with #<TCPSocket:(closed)>
Did not timeout. Got response: Hello, World!

In this case, whichever request has a 10 second timeout succeeds, while the request with a 1 second timeout gets Faraday::TimeoutError. In contrast, when I run this using the httpx adapter, whatever timeout value is set on the first request gets reused on the 2nd request:

$ FARADAY_ADAPTER=httpx TIMEOUTS=10,1 ruby httpx_bug.rb
Did not timeout. Got response: Hello, World!
Did not timeout. Got response: Hello, World!
$ FARADAY_ADAPTER=httpx TIMEOUTS=1,10 ruby httpx_bug.rb
Got Faraday::TimeoutError: Timed out after 1 seconds
Got Faraday::TimeoutError: Timed out after 1 seconds
HoneyryderChuck commented 3 months ago

thx for the report @myronmarston . branch gh-54 contains a fix for your issue.

There is a small caveat in your example, which did not make it "apples to apples", as net-http does not reuse connections, while httpx does, in certain conditions. this should be an exception though, which has been corrected now.