Net::OpenTimeout
is raised when a connection cannot be created within a specified amount of time. Getting this error a few times isn't always out of the ordinary, but you may want to add some error handling.
# net_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 open timeout to 1ms
# i.e. if we can't open a connection within 1ms this will cause a
# Net::OpenTimeout error when the request is made
c.open_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 net_timeout_1.rb
# => ../net/http.rb:904:in `initialize': execution expired (Net::OpenTimeout)
When we execute this code, Net::OpenTimeout
is raised because the TCP connection takes more than 1ms to set up. It is important to set the open_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::OpenTimeout
is raised when step 1 doesn't succeed within the given time.
This error is not the same as Net::ReadTimeout error which is raised when a connection is successfully made, but the response cannot be read within a given amount of time (i.e. when step 3 does not succeed within a given time).
If you (or the library that you use) don't define an open_timeout
, your code will be stuck trying to open a connection (while making an http request) 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::OpenTimeout
, you should handle it by retrying the request a few times, or giving up and showing a helpful error to the user:
# net_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 open timeout to 1ms
# i.e. if we can't open a connection within 1ms this will cause a
# Net::OpenTimeout error when the request is made
c.open_timeout = 0.1
# make a get request after opening a connection
response = c.request_get(path)
# 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/http.rb:904:in `initialize': execution expired (Net::OpenTimeout)
» Too many Net::OpenTimeout
errors
If you get too many of these errors, it could indicate that:
- You have set a low
open_timeout
, which can be fixed by increasing it to a sensible value. - The target endpoint is unable to handle the traffic that you are sending its way, 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 capacity, you can also work around this by using a fixed pool of connections.
» Usage in common gems
Most HTTP clients provide a way to configure open_timeout
; here are a few of the common ones:
» 1. HTTParty
HTTParty allows you to set an open timeout using the code below:
# use an open_timeout of 100ms
HTTParty.get('http://www.example.com', { open_timeout: 0.1 })
# use it in a custom client
class SaneHTTPClient
include HTTParty
open_timeout 1
end
SaneHTTPClient.get("www.example.com")
» 2. Faraday
Faraday allows you to pass open_timeout
as part of the :request
option hash, which throws a Faraday::ConnectionFailed
error in case of an open timeout.
# set the open timeout to 1ms
conn = Faraday.new(url: "http://www.example.com", request: { open_timeout: 0.001 })
conn.get("/index.html")
# => Faraday::ConnectionFailed: execution expired
» 3. REST Client
REST Client offers open_timeout
as an option to the RestClient::Request.execute
function which throws a RestClient::Exceptions::OpenTimeout
in case of a open timeout.
# set the open time to 1ms
RestClient::Request.execute(method: :get, url: 'http://example.com/',
open_timeout: 0.001)
# => RestClient::Exceptions::OpenTimeout: Timed out connecting to server
» More resources
The Ultimate Guide to Ruby Timeouts is a great article which documents how to configure timeouts using popular gems.