diff --git a/lib/net/http/persistent.rb b/lib/net/http/persistent.rb index 51e7bbe..ae83cf7 100644 --- a/lib/net/http/persistent.rb +++ b/lib/net/http/persistent.rb @@ -120,6 +120,13 @@ # The amount of time allowed between reading two chunks from the socket. Set # through #read_timeout # +# === Max Requests +# +# The number of requests that should be made before opening a new connection. +# Typically many keep-alive capable servers tune this to 100 or less, so the +# 101st request will fail with ECONNRESET. If unset (default), this value has no +# effect, if set, connections will be reset on the request after max_requests. +# # === Open Timeout # # The amount of time to wait for a connection to be opened. Set through @@ -322,6 +329,12 @@ def self.detect_idle_timeout uri, max = 10 attr_accessor :idle_timeout + ## + # Maximum number of requests on a connection before it is considered expired + # and automatically closed. + + attr_accessor :max_requests + ## # The value sent in the Keep-Alive header. Defaults to 30. Not needed for # HTTP/1.1 servers. @@ -477,6 +490,7 @@ def initialize name = nil, proxy = nil @open_timeout = nil @read_timeout = nil @idle_timeout = 5 + @max_requests = nil @socket_options = [] @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if @@ -651,10 +665,12 @@ def escape str end ## - # Returns true if the connection should be reset due to an idle timeout, - # false otherwise. + # Returns true if the connection should be reset due to an idle timeout, or + # maximum request count, false otherwise. def expired? connection + requests = Thread.current[@request_key][connection.object_id] + return true if @max_requests && @max_requests >= requests return false unless @idle_timeout return true if @idle_timeout.zero? diff --git a/test/test_net_http_persistent.rb b/test/test_net_http_persistent.rb index 27b659d..208d2a2 100644 --- a/test/test_net_http_persistent.rb +++ b/test/test_net_http_persistent.rb @@ -646,6 +646,7 @@ def test_escape def test_expired_eh c = basic_connection + reqs[c.object_id] = 0 touts[c.object_id] = Time.now - 11 @http.idle_timeout = 0 @@ -664,6 +665,20 @@ def test_expired_eh refute @http.expired? c end + def test_expired_due_to_max_requests + c = basic_connection + reqs[c.object_id] = 0 + touts[c.object_id] = Time.now + + refute @http.expired? c + + reqs[c.object_id] = 10 + refute @http.expired? c + + @http.max_requests = 10 + assert @http.expired? c + end + def test_finish c = basic_connection reqs[c.object_id] = 5