ankane/the-ultimate-guide-to-ruby-timeouts

Net::HTTP read_timeout causes double requests

vassilevsky opened this issue · 4 comments

Hello :)

Test script:

require 'net/http'

uri = URI('http://localhost:4567/foo/bar')

Net::HTTP.start(uri.host, uri.port, read_timeout: 1) do |http|
  http.request(Net::HTTP::Get.new(uri))
end

Test server output:

$ ruby -r sinatra -e 'get("/foo/bar"){ sleep 3; "baz" }'
== Sinatra/1.4.5 has taken the stage on 4567 for development with backup from Thin
Thin web server (v1.6.2 codename Doc Brown)
Maximum connections set to 1024
Listening on localhost:4567, CTRL+C to stop
127.0.0.1 - - [21/Dec/2015:00:03:21 +0300] "GET /foo/bar HTTP/1.1" 200 3 3.0183
127.0.0.1 - - [21/Dec/2015:00:03:22 +0300] "GET /foo/bar HTTP/1.1" 200 3 3.0059

I think this retry statement is to blame:

https://github.com/ruby/ruby/blob/v2_2_4/lib/net/http.rb#L1436

I'm not sure what to do with this information. The behaviour is unexpected. Add a warning maybe?

Great, thanks @vassilevsky! Looks like the ability to configure the retry is an outstanding feature request. https://bugs.ruby-lang.org/issues/10674

Thanks for this, it just solved nearly 6 hours of shaking my fist... The api I was interacting with was slow on a DELETE request, and due to not being idempotent, the double tap was causing an error to be returned due to resource not existing.

I was able to solve via the following:

  def delete_request(url, payload)
    uri = URI(url)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http.read_timeout = 120 # <---- added this to help overcome the issue
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    req = Net::HTTP::Delete.new(uri.path, 'Content-Type' => 'application/json')
    req.body = payload.to_json
    http.request(req)
  end

I have to say that since discovering this peculiarity of Ruby standard HTTP client, I used http gem for all production HTTP interaction where possible. It's good.

So, the fix would be something like this:

require 'net/http'

uri = URI('http://localhost:4567/foo/bar')

Net::HTTP.start(uri.host, uri.port, read_timeout: 1) do |http|
  http.max_retries = 0 # Fix http retries
  http.request(Net::HTTP::Get.new(uri))
end