Net::OpenTimeout

Shows up when things are slow

Ruby's Net::OpenTimeout class

OpenTimeout, a subclass of Timeout::Error, is raised if a connection cannot be created within the open_timeout.

Superclass

Timeout::Error

Enemies

Honeybadger

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:

  1. Open a TCP connection to the endpoint.
  2. Send a request over the connection.
  3. 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:

  1. You have set a low open_timeout, which can be fixed by increasing it to a sensible value.
  2. 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.