Net::ReadTimeout
is raised when a chunk of data cannot be read within a specified amount of time.
# read_timeout_1.rb
require 'net/http'
# create a new http connection object, the connection isn't made yet
c = Net::HTTP.new("www.example.com")
# set the read timeout to 1ms
# i.e. if we can't read the response or a chunk within 1ms this will cause a
# Net::ReadTimeout error when the request is made
c.read_timeout = 0.001
# make a GET request after opening a connection
response = c.request_get("/index.html")
# print the response status code
puts "RESPONSE STATUS CODE: #{response.code}"
$ ruby read_timeout_1.rb
# => ../net/protocol.rb:176:in `rbuf_fill': Net::ReadTimeout (Net::ReadTimeout)
When we execute this code, Net::ReadTimeout
is raised because it takes more than 1 ms for the server to send a response after the connection is set up. It is important to set the read_timeout
to a sensible number of seconds/milliseconds based on your requirements. The default value is 60 seconds.
» In the context of HTTP requests
A successful HTTP request has 3 main steps:
- Open a TCP connection to the endpoint.
- Send a request over the connection.
- Read the response written to the connection.
Net::ReadTimeout
is raised when step 3 doesn't succeed within the given time.
This error is not the same as Net::OpenTimeout
, which is raised when a connection cannot be setup within a given amount of time (i.e. when step 1 does not succeed within a given time).
If you (or the library that you use) don't define a read_timeout
, your code will be stuck trying to read a response from a slow server for a long time when something is wrong with the network or the endpoint. So, setting these timeouts is very important to build predictable systems.
When you run into Net::ReadTimeout
, you should handle it by retrying the request a few times, or giving up and showing a helpful error to the user:
# read_timeout_2.rb
require 'net/http'
def get(host, path, retries = 3)
# create a new http connection object, the connection isn't made yet
c = Net::HTTP.new(host)
# set the read timeout to 1ms
# i.e. if we can't read the response or a chunk within 1ms this will cause a
# Net::ReadTimeout error when the request is made
c.read_timeout = 0.001
# make a GET request after opening a connection
response = c.request_get("/index.html")
# print the response status code
puts "RESPONSE STATUS CODE: #{response.code}"
rescue Net::OpenTimeout => e
puts "TRY #{retries}/n ERROR: timed out while trying to connect #{e}"
if retries <= 1
raise
end
get(host, path, retries - 1)
end
get("www.example.com", "/index.html")
# => ../net/protocol.rb:176:in `rbuf_fill': Net::ReadTimeout (Net::ReadTimeout)
» Too many Net::ReadTimeout
errors
Ideally you shouldn't be getting too many read timeouts if read_timeout
is set to a sensible value. If you get too many of these errors, it could indicate that:
- You have set a low
read_timeout
, which can be fixed by increasing the value. - The target endpoint response times have a lot of deviation, in which case you must either send the traffic in a controlled way (by throttling it), or if the target endpoint is in your control, improve its response time.
» Usage in common gems
Most HTTP clients provide a way to configure read_timeout
; here are a few of the common ones:
» 1. HTTParty
HTTParty allows you to set a read timeout using the code below:
# use a read_timeout of 100ms
HTTParty.get('http://www.example.com', { read_timeout: 0.1 })
# use it in a custom client
class SaneHTTPClient
include HTTParty
read_timeout 1
end
SaneHTTPClient.get("www.example.com")
» 2. Faraday
Faraday allows you to pass read timeout using the timeout
option as part of the :request
option hash, which throws a Faraday::TimeoutError
error in case of a read timeout.
# set the read timeout to 1ms
conn = Faraday.new(url: "http://www.example.com", request: { timeout: 0.001 })
conn.get("/index.html")
# => Faraday::TimeoutError: Net::ReadTimeout
» 3. REST Client
REST Client offers read_timeout
as an option to the RestClient::Request.execute
function which throws a RestClient::Exceptions::ReadTimeout
error in case of a read timeout.
# set the read timeout to 1ms
RestClient::Request.execute(method: :get, url: 'http://example.com/',
read_timeout: 0.001)
# => RestClient::Exceptions::ReadTimeout: Timed out reading data from server
» Pedantic Note
read_timeout
usually specifies the time to read responses while making HTTP requests. However, in case of chunked responses, this timeout applies just for reading a single chunk of the response. So, if we have an HTTP server which streams data by sending a chunk every second and the whole response takes 10 minutes, setting a read_timeout
to 2 seconds will not error out because we are receiving a chunk every second. So, don't be surprised if a read_timeout
of 2 seconds doesn't error out even after 10 minutes while using chunked responses.
» More resources
The Ultimate Guide to Ruby Timeouts is a great article which documents how to add timeouts while using popular gems.