Permalink
Browse files

Use a connection pool to manage connections

We now use the connection_pool gem instead of per-thread connection
stores.  This improves reuse of connections across threads and reduces
the possibility of using all the file descriptors in the process.

This removes the Thread argument for #shutdown (which now shuts down all
connections and may be dangerous, needs verification).

This removes #shutdown_in_all_threads as we no longer have per-thread
storage.

This adds the Net::HTTP::Persistent::Connection class to manage
connection metadata.

This makes Net::HTTP::Persistent's tests a little easier to read as you
no longer have to worry about the per-thread data stores.
  • Loading branch information...
1 parent 31b71a4 commit 5bae22e8a3626ab3b14743c00796d4f176580ad8 @drbrain committed Apr 8, 2015
View
4 Manifest.txt
@@ -6,6 +6,10 @@ README.rdoc
Rakefile
lib/net/http/faster.rb
lib/net/http/persistent.rb
+lib/net/http/persistent/connection.rb
+lib/net/http/persistent/pool.rb
lib/net/http/persistent/ssl_reuse.rb
+lib/net/http/persistent/timed_stack_multi.rb
test/test_net_http_persistent.rb
test/test_net_http_persistent_ssl_reuse.rb
+test/test_net_http_persistent_timed_stack_multi.rb
View
3 Rakefile
@@ -18,7 +18,8 @@ Hoe.spec 'net-http-persistent' do
rdoc_locations <<
'docs.seattlerb.org:/data/www/docs.seattlerb.org/net-http-persistent/'
- dependency 'minitest', '~> 5.2', :development
+ dependency 'connection_pool', '~> 2.1'
+ dependency 'minitest', '~> 5.2', :development
end
# vim: syntax=Ruby
View
276 lib/net/http/persistent.rb
@@ -7,6 +7,7 @@
require 'net/http/faster'
require 'uri'
require 'cgi' # for escaping
+require 'connection_pool'
begin
require 'net/http/pipeline'
@@ -252,31 +253,31 @@ def self.detect_idle_timeout uri, max = 10
http = new 'net-http-persistent detect_idle_timeout'
- connection = http.connection_for uri
+ http.connection_for uri do |connection|
+ sleep_time = 0
- sleep_time = 0
+ http = connection.http
- loop do
- response = connection.request req
+ loop do
+ response = http.request req
- $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG
+ $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG
- unless Net::HTTPOK === response then
- raise Error, "bad response code #{response.code} detecting idle timeout"
- end
+ unless Net::HTTPOK === response then
+ raise Error, "bad response code #{response.code} detecting idle timeout"
+ end
- break if sleep_time >= max
+ break if sleep_time >= max
- sleep_time += 1
+ sleep_time += 1
- $stderr.puts "sleeping #{sleep_time}" if $DEBUG
- sleep sleep_time
+ $stderr.puts "sleeping #{sleep_time}" if $DEBUG
+ sleep sleep_time
+ end
end
rescue
# ignore StandardErrors, we've probably found the idle timeout.
ensure
- http.shutdown
-
return sleep_time unless $!
end
@@ -398,14 +399,14 @@ def self.detect_idle_timeout uri, max = 10
attr_reader :no_proxy
##
- # Seconds to wait until reading one block. See Net::HTTP#read_timeout
+ # Test-only accessor for the connection pool
- attr_accessor :read_timeout
+ attr_reader :pool # :nodoc:
##
- # Where this instance's request counts live in the thread local variables
+ # Seconds to wait until reading one block. See Net::HTTP#read_timeout
- attr_reader :request_key # :nodoc:
+ attr_accessor :read_timeout
##
# By default SSL sessions are reused to avoid extra SSL handshakes. Set
@@ -519,16 +520,21 @@ def initialize name = nil, proxy = nil
@idle_timeout = 5
@max_requests = nil
@socket_options = []
+ @ssl_generation = 0 # incremented when SSL session variables change
@socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if
Socket.const_defined? :TCP_NODELAY
key = ['net_http_persistent', name].compact
@generation_key = [key, 'generations' ].join('_').intern
@ssl_generation_key = [key, 'ssl_generations'].join('_').intern
- @request_key = [key, 'requests' ].join('_').intern
@timeout_key = [key, 'timeouts' ].join('_').intern
+ pool_size = Process.getrlimit(Process::RLIMIT_NOFILE).first / 4
+ @pool = Net::HTTP::Persistent::Pool.new size: pool_size do |http_args|
+ Net::HTTP::Persistent::Connection.new http_class, http_args, @ssl_generation
+ end
+
@certificate = nil
@ca_file = nil
@ca_path = nil
@@ -542,7 +548,6 @@ def initialize name = nil, proxy = nil
@cert_store = nil
@generation = 0 # incremented when proxy URI changes
- @ssl_generation = 0 # incremented when SSL session variables change
if HAVE_OPENSSL then
@verify_mode = OpenSSL::SSL::VERIFY_PEER
@@ -633,77 +638,57 @@ def cleanup(generation, thread = Thread.current,
# Creates a new connection for +uri+
def connection_for uri
- Thread.current[@generation_key] ||= Hash.new { |h,k| h[k] = {} }
- Thread.current[@ssl_generation_key] ||= Hash.new { |h,k| h[k] = {} }
- Thread.current[@request_key] ||= Hash.new 0
Thread.current[@timeout_key] ||= Hash.new EPOCH
use_ssl = uri.scheme.downcase == 'https'
- if use_ssl then
- raise Net::HTTP::Persistent::Error, 'OpenSSL is not available' unless
- HAVE_OPENSSL
-
- ssl_generation = @ssl_generation
-
- ssl_cleanup ssl_generation
-
- connections = Thread.current[@ssl_generation_key][ssl_generation]
- else
- generation = @generation
-
- cleanup generation
+ net_http_args = [uri.host, uri.port]
- connections = Thread.current[@generation_key][generation]
- end
+ net_http_args.concat @proxy_args if
+ @proxy_uri and not proxy_bypass? uri.host, uri.port
- net_http_args = [uri.host, uri.port]
- connection_id = net_http_args.join ':'
+ connection = @pool.checkout net_http_args
- if @proxy_uri and not proxy_bypass? uri.host, uri.port then
- connection_id << @proxy_connection_id
- net_http_args.concat @proxy_args
- end
+ http = connection.http
- connection = connections[connection_id]
+ connection.ressl @ssl_generation if
+ connection.ssl_generation != @ssl_generation
- unless connection = connections[connection_id] then
- connections[connection_id] = http_class.new(*net_http_args)
- connection = connections[connection_id]
- ssl connection if use_ssl
- else
- reset connection if expired? connection
+ if not http.started? then
+ ssl http if use_ssl
+ start http
+ elsif expired? connection then
+ reset connection
end
- start connection unless connection.started?
-
- connection.read_timeout = @read_timeout if @read_timeout
- connection.keep_alive_timeout = @idle_timeout if @idle_timeout && connection.respond_to?(:keep_alive_timeout=)
+ http.read_timeout = @read_timeout if @read_timeout
+ http.keep_alive_timeout = @idle_timeout if @idle_timeout && http.respond_to?(:keep_alive_timeout=)
- connection
+ return yield connection
rescue Errno::ECONNREFUSED
- address = connection.proxy_address || connection.address
- port = connection.proxy_port || connection.port
+ address = http.proxy_address || http.address
+ port = http.proxy_port || http.port
raise Error, "connection refused: #{address}:#{port}"
rescue Errno::EHOSTDOWN
- address = connection.proxy_address || connection.address
- port = connection.proxy_port || connection.port
+ address = http.proxy_address || http.address
+ port = http.proxy_port || http.port
raise Error, "host down: #{address}:#{port}"
+ ensure
+ @pool.checkin net_http_args
end
##
# Returns an error message containing the number of requests performed on
# this connection
def error_message connection
- requests = Thread.current[@request_key][connection.object_id] - 1 # fixup
- last_use = Thread.current[@timeout_key][connection.object_id]
+ connection.requests -= 1 # fixup
- age = Time.now - last_use
+ age = Time.now - connection.last_use
- "after #{requests} requests on #{connection.object_id}, " \
+ "after #{connection.requests} requests on #{connection.http.object_id}, " \
"last used #{age} seconds ago"
end
@@ -727,26 +712,23 @@ def unescape str
# maximum request count, false otherwise.
def expired? connection
- requests = Thread.current[@request_key][connection.object_id]
- return true if @max_requests && requests >= @max_requests
+ return true if @max_requests && connection.requests >= @max_requests
return false unless @idle_timeout
return true if @idle_timeout.zero?
- last_used = Thread.current[@timeout_key][connection.object_id]
-
- Time.now - last_used > @idle_timeout
+ Time.now - connection.last_use > @idle_timeout
end
##
# Starts the Net::HTTP +connection+
- def start connection
- connection.set_debug_output @debug_output if @debug_output
- connection.open_timeout = @open_timeout if @open_timeout
+ def start http
+ http.set_debug_output @debug_output if @debug_output
+ http.open_timeout = @open_timeout if @open_timeout
- connection.start
+ http.start
- socket = connection.instance_variable_get :@socket
+ socket = http.instance_variable_get :@socket
if socket then # for fakeweb
@socket_options.each do |option|
@@ -758,13 +740,8 @@ def start connection
##
# Finishes the Net::HTTP +connection+
- def finish connection, thread = Thread.current
- if requests = thread[@request_key] then
- requests.delete connection.object_id
- end
-
+ def finish connection
connection.finish
- rescue IOError
end
def http_class # :nodoc:
@@ -869,9 +846,9 @@ def normalize_uri uri
# <tt>net-http-persistent</tt> #pipeline will be present.
def pipeline uri, requests, &block # :yields: responses
- connection = connection_for uri
-
- connection.pipeline requests, &block
+ connection_for uri do |connection|
+ connection.http.pipeline requests, &block
+ end
end
##
@@ -1004,18 +981,17 @@ def reconnect_ssl
# Finishes then restarts the Net::HTTP +connection+
def reset connection
- Thread.current[@request_key].delete connection.object_id
- Thread.current[@timeout_key].delete connection.object_id
+ http = connection.http
finish connection
- start connection
+ start http
rescue Errno::ECONNREFUSED
- e = Error.new "connection refused: #{connection.address}:#{connection.port}"
+ e = Error.new "connection refused: #{http.address}:#{http.port}"
e.set_backtrace $@
raise e
rescue Errno::EHOSTDOWN
- e = Error.new "host down: #{connection.address}:#{connection.port}"
+ e = Error.new "host down: #{http.address}:#{http.port}"
e.set_backtrace $@
raise e
end
@@ -1036,52 +1012,55 @@ def request uri, req = nil, &block
retried = false
bad_response = false
- req = request_setup req || uri
+ req = request_setup req || uri
+ response = nil
- connection = connection_for uri
- connection_id = connection.object_id
+ connection_for uri do |connection|
+ http = connection.http
- begin
- Thread.current[@request_key][connection_id] += 1
- response = connection.request req, &block
+ begin
+ connection.requests += 1
- if connection_close?(req) or
- (response.http_version <= '1.0' and
- not connection_keep_alive?(response)) or
- connection_close?(response) then
- connection.finish
- end
- rescue Net::HTTPBadResponse => e
- message = error_message connection
+ response = http.request req, &block
- finish connection
+ if connection_close?(req) or
+ (response.http_version <= '1.0' and
+ not connection_keep_alive?(response)) or
+ connection_close?(response) then
+ finish connection
+ end
+ rescue Net::HTTPBadResponse => e
+ message = error_message connection
- raise Error, "too many bad responses #{message}" if
+ finish connection
+
+ raise Error, "too many bad responses #{message}" if
bad_response or not can_retry? req
- bad_response = true
- retry
- rescue *RETRIED_EXCEPTIONS => e # retried on ruby 2
- request_failed e, req, connection if
- retried or not can_retry? req, @retried_on_ruby_2
+ bad_response = true
+ retry
+ rescue *RETRIED_EXCEPTIONS => e # retried on ruby 2
+ request_failed e, req, connection if
+ retried or not can_retry? req, @retried_on_ruby_2
- reset connection
+ reset connection
- retried = true
- retry
- rescue Errno::EINVAL, Errno::ETIMEDOUT => e # not retried on ruby 2
- request_failed e, req, connection if retried or not can_retry? req
+ retried = true
+ retry
+ rescue Errno::EINVAL, Errno::ETIMEDOUT => e # not retried on ruby 2
+ request_failed e, req, connection if retried or not can_retry? req
- reset connection
+ reset connection
- retried = true
- retry
- rescue Exception => e
- finish connection
+ retried = true
+ retry
+ rescue Exception => e
+ finish connection
- raise
- ensure
- Thread.current[@timeout_key][connection_id] = Time.now
+ raise
+ ensure
+ connection.last_use = Time.now
+ end
end
@http_versions["#{uri.host}:#{uri.port}"] ||= response.http_version
@@ -1101,7 +1080,6 @@ def request_failed exception, req, connection # :nodoc:
finish connection
-
raise Error, message, exception.backtrace
end
@@ -1135,45 +1113,17 @@ def request_setup req_or_uri # :nodoc:
end
##
- # Shuts down all connections for +thread+.
- #
- # Uses the current thread by default.
- #
- # If you've used Net::HTTP::Persistent across multiple threads you should
- # call this in each thread when you're done making HTTP requests.
- #
- # *NOTE*: Calling shutdown for another thread can be dangerous!
- #
- # If the thread is still using the connection it may cause an error! It is
- # best to call #shutdown in the thread at the appropriate time instead!
-
- def shutdown thread = Thread.current
- generation = reconnect
- cleanup generation, thread, @generation_key
-
- ssl_generation = reconnect_ssl
- cleanup ssl_generation, thread, @ssl_generation_key
-
- thread[@request_key] = nil
- thread[@timeout_key] = nil
- end
-
- ##
- # Shuts down all connections in all threads
+ # Shuts down all connections
#
- # *NOTE*: THIS METHOD IS VERY DANGEROUS!
+ # *NOTE*: Calling shutdown for can be dangerous!
#
- # Do not call this method if other threads are still using their
- # connections! Call #shutdown at the appropriate time instead!
- #
- # Use this method only as a last resort!
+ # If any thread is still using a connection it may cause an error! Call
+ # #shutdown when you are completely done making requests!
- def shutdown_in_all_threads
- Thread.list.each do |thread|
- shutdown thread
+ def shutdown
+ @pool.available.shutdown do |http|
+ http.finish
end
-
- nil
end
##
@@ -1239,14 +1189,6 @@ def ssl connection
end
##
- # Finishes all connections that existed before the given SSL parameter
- # +generation+.
-
- def ssl_cleanup generation # :nodoc:
- cleanup generation, Thread.current, @ssl_generation_key
- end
-
- ##
# SSL session lifetime
def ssl_timeout= ssl_timeout
@@ -1297,5 +1239,7 @@ def verify_callback= callback
end
+require 'net/http/persistent/connection'
+require 'net/http/persistent/pool'
require 'net/http/persistent/ssl_reuse'
View
40 lib/net/http/persistent/connection.rb
@@ -0,0 +1,40 @@
+##
+# A Net::HTTP connection wrapper that holds extra information for managing the
+# connection's lifetime.
+
+class Net::HTTP::Persistent::Connection # :nodoc:
+
+ attr_accessor :http
+
+ attr_accessor :last_use
+
+ attr_accessor :requests
+
+ attr_accessor :ssl_generation
+
+ def initialize http_class, http_args, ssl_generation
+ @http = http_class.new(*http_args)
+ @ssl_generation = ssl_generation
+
+ reset
+ end
+
+ def finish
+ @http.finish
+ rescue IOError
+ ensure
+ reset
+ end
+
+ def reset
+ @last_use = Net::HTTP::Persistent::EPOCH
+ @requests = 0
+ end
+
+ def ressl ssl_generation
+ @ssl_generation = ssl_generation
+
+ finish
+ end
+
+end
View
46 lib/net/http/persistent/pool.rb
@@ -0,0 +1,46 @@
+class Net::HTTP::Persistent::Pool < ConnectionPool # :nodoc:
+
+ attr_reader :available # :nodoc:
+ attr_reader :key # :nodoc:
+
+ def initialize(options = {}, &block)
+ super
+
+ @available = Net::HTTP::Persistent::TimedStackMulti.new(@size, &block)
+ @key = :"current-#{@available.object_id}"
+ end
+
+ def checkin net_http_args
+ stack = Thread.current[@key][net_http_args]
+
+ raise ConnectionPool::Error, 'no connections are checked out' if
+ stack.empty?
+
+ conn = stack.pop
+
+ if stack.empty?
+ @available.push conn, connection_args: net_http_args
+ end
+
+ nil
+ end
+
+ def checkout net_http_args
+ stacks = Thread.current[@key] ||= Hash.new { |h, k| h[k] = [] }
+ stack = stacks[net_http_args]
+
+ if stack.empty? then
+ conn = @available.pop connection_args: net_http_args
+ else
+ conn = stack.last
+ end
+
+ stack.push conn
+
+ conn
+ end
+
+end
+
+require 'net/http/persistent/timed_stack_multi'
+
View
69 lib/net/http/persistent/timed_stack_multi.rb
@@ -0,0 +1,69 @@
+class Net::HTTP::Persistent::TimedStackMulti < ConnectionPool::TimedStack
+
+ def initialize(size = 0, &block)
+ super
+
+ @enqueued = 0
+ @ques = Hash.new { |h, k| h[k] = [] }
+ @lru = {}
+ @key = :"connection_args-#{object_id}"
+ end
+
+ def empty?
+ (@created - @enqueued) >= @max
+ end
+
+ def length
+ @max - @created + @enqueued
+ end
+
+ private
+
+ def connection_stored? options = {} # :nodoc:
+ !@ques[options[:connection_args]].empty?
+ end
+
+ def fetch_connection options = {} # :nodoc:
+ connection_args = options[:connection_args]
+
+ @enqueued -= 1
+ lru_update connection_args
+ @ques[connection_args].pop
+ end
+
+ def lru_update connection_args # :nodoc:
+ @lru.delete connection_args
+ @lru[connection_args] = true
+ end
+
+ def shutdown_connections # :nodoc:
+ @ques.each_key do |key|
+ super connection_args: key
+ end
+ end
+
+ def store_connection obj, options = {} # :nodoc:
+ @ques[options[:connection_args]].push obj
+ @enqueued += 1
+ end
+
+ def try_create options = {} # :nodoc:
+ connection_args = options[:connection_args]
+
+ if @created >= @max && @enqueued >= 1
+ oldest, = @lru.first
+ @lru.delete oldest
+ @ques[oldest].pop
+
+ @created -= 1
+ end
+
+ if @created < @max
+ @created += 1
+ lru_update connection_args
+ return @create_block.call(connection_args)
+ end
+ end
+
+end
+
View
690 test/test_net_http_persistent.rb
@@ -84,21 +84,21 @@ def setup
end
def teardown
- Thread.current.keys.each do |key|
- Thread.current[key] = nil
- end
-
Net::HTTP.use_connect :orig_connect
Net::HTTP::Persistent::SSLReuse.use_connect :orig_connect
end
class BasicConnection
- attr_accessor :started, :finished, :address, :port,
+ attr_accessor :started, :finished, :address, :port, :use_ssl,
:read_timeout, :open_timeout
+ attr_accessor :ciphers, :ssl_timeout, :ssl_version,
+ :verify_depth, :verify_mode, :cert_store,
+ :ca_file, :ca_path, :cert, :key
attr_reader :req, :debug_output
def initialize
@started, @finished = 0, 0
@address, @port = 'example.com', 80
+ @use_ssl = false
end
def finish
@finished += 1
@@ -125,21 +125,32 @@ def io.setsockopt(*a) @setsockopts ||= []; @setsockopts << a end
def started?
@started >= 1
end
+ def proxy_address
+ end
+ def proxy_port
+ end
end
def basic_connection
raise "#{@uri} is not HTTP" unless @uri.scheme.downcase == 'http'
- c = BasicConnection.new
- conns[0]["#{@uri.host}:#{@uri.port}"] = c
- c
+ net_http_args = [@uri.host, @uri.port]
+
+ connection = Net::HTTP::Persistent::Connection.allocate
+ connection.ssl_generation = @http.ssl_generation
+ connection.http = BasicConnection.new
+ connection.reset
+
+ @http.pool.available.push connection, connection_args: net_http_args
+
+ connection
end
def connection
- c = basic_connection
- touts[c.object_id] = Time.now
+ connection = basic_connection
+ connection.last_use = Time.now
- def c.request(req)
+ def (connection.http).request(req)
@req = req
r = Net::HTTPResponse.allocate
r.instance_variable_set :@header, {}
@@ -149,30 +160,22 @@ def r.read_body() :read_body end
r
end
- c
+ connection
end
- def conns
- Thread.current[@http.generation_key] ||= Hash.new { |h,k| h[k] = {} }
- end
+ def ssl_connection
+ raise "#{@uri} is not HTTPS" unless @uri.scheme.downcase == 'https'
- def reqs
- Thread.current[@http.request_key] ||= Hash.new 0
- end
+ net_http_args = [@uri.host, @uri.port]
- def ssl_conns
- Thread.current[@http.ssl_generation_key] ||= Hash.new { |h,k| h[k] = {} }
- end
+ connection = Net::HTTP::Persistent::Connection.allocate
+ connection.ssl_generation = @http.ssl_generation
+ connection.http = BasicConnection.new
+ connection.reset
- def ssl_connection generation = 0
- raise "#{@uri} is not HTTPS" unless @uri.scheme.downcase == 'https'
- c = BasicConnection.new
- ssl_conns[generation]["#{@uri.host}:#{@uri.port}"] = c
- c
- end
+ @http.pool.available.push connection, connection_args: net_http_args
- def touts
- Thread.current[@http.timeout_key] ||= Hash.new Net::HTTP::Persistent::EPOCH
+ connection
end
def test_initialize
@@ -289,162 +292,154 @@ def test_connection_for
@http.open_timeout = 123
@http.read_timeout = 321
@http.idle_timeout = 42
- c = @http.connection_for @uri
- assert_kind_of @http_class, c
+ used = @http.connection_for @uri do |c|
+ assert_kind_of @http_class, c.http
+
+ assert c.http.started?
+ refute c.http.proxy?
+
+ assert_equal 123, c.http.open_timeout
+ assert_equal 321, c.http.read_timeout
+ assert_equal 42, c.http.keep_alive_timeout unless RUBY_1
- assert c.started?
- refute c.proxy?
+ c
+ end
- assert_equal 123, c.open_timeout
- assert_equal 321, c.read_timeout
- assert_equal 42, c.keep_alive_timeout unless RUBY_1
+ stored = @http.pool.checkout ['example.com', 80]
- assert_includes conns[0].keys, 'example.com:80'
- assert_same c, conns[0]['example.com:80']
+ assert_same used, stored
end
def test_connection_for_cached
cached = basic_connection
- cached.start
- conns[0]['example.com:80'] = cached
+ cached.http.start
@http.read_timeout = 5
- c = @http.connection_for @uri
-
- assert c.started?
+ @http.connection_for @uri do |c|
+ assert c.http.started?
- assert_equal 5, c.read_timeout
+ assert_equal 5, c.http.read_timeout
- assert_same cached, c
+ assert_same cached, c
+ end
end
def test_connection_for_closed
cached = basic_connection
- cached.start
+ cached.http.start
if Socket.const_defined? :TCP_NODELAY then
io = Object.new
def io.setsockopt(*a) raise IOError, 'closed stream' end
cached.instance_variable_set :@socket, Net::BufferedIO.new(io)
end
- conns['example.com:80'] = cached
-
- c = @http.connection_for @uri
- assert c.started?
+ @http.connection_for @uri do |c|
+ assert c.http.started?
- assert_includes conns.keys, 'example.com:80'
- assert_same c, conns[0]['example.com:80']
+ socket = c.http.instance_variable_get :@socket
- socket = c.instance_variable_get :@socket
-
- refute_includes socket.io.instance_variables, :@setsockopt
- refute_includes socket.io.instance_variables, '@setsockopt'
+ refute_includes socket.io.instance_variables, :@setsockopt
+ refute_includes socket.io.instance_variables, '@setsockopt'
+ end
end
def test_connection_for_debug_output
io = StringIO.new
@http.debug_output = io
- c = @http.connection_for @uri
-
- assert c.started?
- assert_equal io, c.instance_variable_get(:@debug_output)
-
- assert_includes conns[0].keys, 'example.com:80'
- assert_same c, conns[0]['example.com:80']
+ @http.connection_for @uri do |c|
+ assert c.http.started?
+ assert_equal io, c.http.instance_variable_get(:@debug_output)
+ end
end
def test_connection_for_cached_expire_always
cached = basic_connection
- cached.start
- conns[0]['example.com:80'] = cached
- reqs[cached.object_id] = 10
- touts[cached.object_id] = Time.now # last used right now
+ cached.http.start
+ cached.requests = 10
+ cached.last_use = Time.now # last used right now
@http.idle_timeout = 0
- c = @http.connection_for @uri
-
- assert c.started?
+ @http.connection_for @uri do |c|
+ assert c.http.started?
- assert_same cached, c
+ assert_same cached, c
- assert_equal 0, reqs[cached.object_id],
- 'connection reset due to timeout'
+ assert_equal 0, c.requests, 'connection reset due to timeout'
+ end
end
def test_connection_for_cached_expire_never
cached = basic_connection
- cached.start
- conns[0]['example.com:80'] = cached
- reqs[cached.object_id] = 10
- touts[cached.object_id] = Time.now # last used right now
+ cached.http.start
+ cached.requests = 10
+ cached.last_use = Time.now # last used right now
@http.idle_timeout = nil
- c = @http.connection_for @uri
+ @http.connection_for @uri do |c|
+ assert c.http.started?
- assert c.started?
+ assert_same cached, c
- assert_same cached, c
-
- assert_equal 10, reqs[cached.object_id],
- 'connection reset despite no timeout'
+ assert_equal 10, c.requests, 'connection reset despite no timeout'
+ end
end
def test_connection_for_cached_expired
cached = basic_connection
- cached.start
- conns[0]['example.com:80'] = cached
- reqs[cached.object_id] = 10
- touts[cached.object_id] = Time.now - 3600
+ cached.http.start
+ cached.requests = 10
+ cached.last_use = Time.now - 3600
- c = @http.connection_for @uri
+ @http.connection_for @uri do |c|
+ assert c.http.started?
- assert c.started?
-
- assert_same cached, c
- assert_equal 0, reqs[cached.object_id],
- 'connection not reset due to timeout'
+ assert_same cached, c
+ assert_equal 0, cached.requests, 'connection not reset due to timeout'
+ end
end
def test_connection_for_finished_ssl
skip 'OpenSSL is missing' unless HAVE_OPENSSL
uri = URI.parse 'https://example.com/path'
- c = @http.connection_for uri
- assert c.started?
- assert c.use_ssl?
-
- @http.finish c
+ @http.connection_for uri do |c|
+ assert c.http.started?
+ assert c.http.use_ssl?
- refute c.started?
+ @http.finish c
- c2 = @http.connection_for uri
+ refute c.http.started?
+ end
- assert c2.started?
+ @http.connection_for uri do |c2|
+ assert c2.http.started?
+ end
end
def test_connection_for_host_down
- cached = basic_connection
- def cached.start; raise Errno::EHOSTDOWN end
- def cached.started?; false end
- conns[0]['example.com:80'] = cached
+ c = basic_connection
+ def (c.http).start; raise Errno::EHOSTDOWN end
+ def (c.http).started?; false end
e = assert_raises Net::HTTP::Persistent::Error do
- @http.connection_for @uri
+ @http.connection_for @uri do end
end
assert_equal 'host down: example.com:80', e.message
end
def test_connection_for_http_class_with_fakeweb
Object.send :const_set, :FakeWeb, nil
- c = @http.connection_for @uri
- assert_instance_of Net::HTTP, c
+
+ @http.connection_for @uri do |c|
+ assert_instance_of Net::HTTP, c.http
+ end
ensure
if Object.const_defined?(:FakeWeb) then
Object.send :remove_const, :FakeWeb
@@ -453,8 +448,9 @@ def test_connection_for_http_class_with_fakeweb
def test_connection_for_http_class_with_webmock
Object.send :const_set, :WebMock, nil
- c = @http.connection_for @uri
- assert_instance_of Net::HTTP, c
+ @http.connection_for @uri do |c|
+ assert_instance_of Net::HTTP, c.http
+ end
ensure
if Object.const_defined?(:WebMock) then
Object.send :remove_const, :WebMock
@@ -463,8 +459,9 @@ def test_connection_for_http_class_with_webmock
def test_connection_for_http_class_with_artifice
Object.send :const_set, :Artifice, nil
- c = @http.connection_for @uri
- assert_instance_of Net::HTTP, c
+ @http.connection_for @uri do |c|
+ assert_instance_of Net::HTTP, c.http
+ end
ensure
if Object.const_defined?(:Artifice) then
Object.send :remove_const, :Artifice
@@ -475,20 +472,19 @@ def test_connection_for_name
http = Net::HTTP::Persistent.new 'name'
uri = URI.parse 'http://example/'
- c = http.connection_for uri
-
- assert c.started?
-
- refute_includes conns.keys, 'example:80'
+ http.connection_for uri do |c|
+ assert c.http.started?
+ end
end
def test_connection_for_no_ssl_reuse
@http.reuse_ssl_sessions = false
@http.open_timeout = 123
@http.read_timeout = 321
- c = @http.connection_for @uri
- assert_instance_of Net::HTTP, c
+ @http.connection_for @uri do |c|
+ assert_instance_of Net::HTTP, c.http
+ end
end
def test_connection_for_proxy
@@ -498,14 +494,18 @@ def test_connection_for_proxy
http = Net::HTTP::Persistent.new nil, uri
- c = http.connection_for @uri
+ used = http.connection_for @uri do |c|
+ assert c.http.started?
+ assert c.http.proxy?
- assert c.started?
- assert c.proxy?
+ c
+ end
+
+ stored = http.pool.checkout ['example.com', 80,
+ 'proxy.example', 80,
+ 'johndoe', 'muffins']
- assert_includes conns[1].keys,
- 'example.com:80:proxy.example:80:johndoe:muffins'
- assert_same c, conns[1]['example.com:80:proxy.example:80:johndoe:muffins']
+ assert_same used, stored
end
def test_connection_for_proxy_unescaped
@@ -515,10 +515,14 @@ def test_connection_for_proxy_unescaped
uri.freeze
http = Net::HTTP::Persistent.new nil, uri
- http.connection_for @uri
- assert_includes conns[1].keys,
- 'example.com:80:proxy.example:80:john@doe:muf:fins'
+ http.connection_for @uri do end
+
+ stored = http.pool.checkout ['example.com', 80,
+ 'proxy.example', 80,
+ 'john@doe', 'muf:fins']
+
+ assert stored
end
def test_connection_for_proxy_host_down
@@ -532,7 +536,7 @@ def test_connection_for_proxy_host_down
http = Net::HTTP::Persistent.new nil, uri
e = assert_raises Net::HTTP::Persistent::Error do
- http.connection_for @uri
+ http.connection_for @uri do end
end
assert_equal 'host down: proxy.example:80', e.message
@@ -549,7 +553,7 @@ def test_connection_for_proxy_refused
http = Net::HTTP::Persistent.new nil, uri
e = assert_raises Net::HTTP::Persistent::Error do
- http.connection_for @uri
+ http.connection_for @uri do end
end
assert_equal 'connection refused: proxy.example:80', e.message
@@ -563,21 +567,22 @@ def test_connection_for_no_proxy
http = Net::HTTP::Persistent.new nil, uri
- c = http.connection_for @uri
+ http.connection_for @uri do |c|
+ assert c.http.started?
+ refute c.http.proxy?
+ end
- assert c.started?
- refute c.proxy?
+ stored = http.pool.checkout ['example.com', 80]
- assert_includes conns[1].keys, 'example.com:80'
- assert_same c, conns[1]['example.com:80']
+ assert stored
end
def test_connection_for_refused
Net::HTTP.use_connect :refused_connect
Net::HTTP::Persistent::SSLReuse.use_connect :refused_connect
e = assert_raises Net::HTTP::Persistent::Error do
- @http.connection_for @uri
+ @http.connection_for @uri do end
end
assert_equal 'connection refused: example.com:80', e.message
@@ -587,22 +592,23 @@ def test_connection_for_ssl
skip 'OpenSSL is missing' unless HAVE_OPENSSL
uri = URI.parse 'https://example.com/path'
- c = @http.connection_for uri
- assert c.started?
- assert c.use_ssl?
+ @http.connection_for uri do |c|
+ assert c.http.started?
+ assert c.http.use_ssl?
+ end
end
def test_connection_for_ssl_cached
skip 'OpenSSL is missing' unless HAVE_OPENSSL
@uri = URI.parse 'https://example.com/path'
- cached = ssl_connection 0
-
- c = @http.connection_for @uri
+ cached = ssl_connection
- assert_same cached, c
+ @http.connection_for @uri do |c|
+ assert_same cached, c
+ end
end
def test_connection_for_ssl_cached_reconnect
@@ -612,45 +618,47 @@ def test_connection_for_ssl_cached_reconnect
cached = ssl_connection
- @http.reconnect_ssl
+ ssl_generation = @http.ssl_generation
- c = @http.connection_for @uri
+ @http.reconnect_ssl
- refute_same cached, c
+ @http.connection_for @uri do |c|
+ assert_same cached, c
+ refute_equal ssl_generation, c.ssl_generation
+ end
end
def test_connection_for_ssl_case
skip 'OpenSSL is missing' unless HAVE_OPENSSL
uri = URI.parse 'HTTPS://example.com/path'
- c = @http.connection_for uri
-
- assert c.started?
- assert c.use_ssl?
+ @http.connection_for uri do |c|
+ assert c.http.started?
+ assert c.http.use_ssl?
+ end
end
def test_connection_for_timeout
cached = basic_connection
- cached.start
- reqs[cached.object_id] = 10
- touts[cached.object_id] = Time.now - 6
- conns[0]['example.com:80'] = cached
+ cached.http.start
+ cached.requests = 10
+ cached.last_use = Time.now - 6
- c = @http.connection_for @uri
+ @http.connection_for @uri do |c|
+ assert c.http.started?
+ assert_equal 0, c.requests
- assert c.started?
- assert_equal 0, reqs[c.object_id]
-
- assert_same cached, c
+ assert_same cached, c
+ end
end
def test_error_message
c = basic_connection
- touts[c.object_id] = Time.now - 1
- reqs[c.object_id] = 5
+ c.last_use = Time.now - 1
+ c.requests = 5
- message = @http.error_message(c)
- assert_match %r%after 4 requests on #{c.object_id}%, message
+ message = @http.error_message c
+ assert_match %r%after 4 requests on #{c.http.object_id}%, message
assert_match %r%, last used [\d.]+ seconds ago%, message
end
@@ -668,8 +676,8 @@ def test_unescape
def test_expired_eh
c = basic_connection
- reqs[c.object_id] = 0
- touts[c.object_id] = Time.now - 11
+ c.requests = 0
+ c.last_use = Time.now - 11
@http.idle_timeout = 0
assert @http.expired? c
@@ -689,41 +697,43 @@ def test_expired_eh
def test_expired_due_to_max_requests
c = basic_connection
- reqs[c.object_id] = 0
- touts[c.object_id] = Time.now
+ c.requests = 0
+ c.last_use = Time.now
refute @http.expired? c
- reqs[c.object_id] = 10
+ c.requests = 10
refute @http.expired? c
@http.max_requests = 10
assert @http.expired? c
- reqs[c.object_id] = 9
+ c.requests = 9
refute @http.expired? c
end
def test_finish
c = basic_connection
- reqs[c.object_id] = 5
+ c.requests = 5
@http.finish c
- refute c.started?
- assert c.finished?
- assert_equal 0, reqs[c.object_id]
+ refute c.http.started?
+ assert c.http.finished?
+
+ assert_equal 0, c.requests
+ assert_equal Net::HTTP::Persistent::EPOCH, c.last_use
end
def test_finish_io_error
c = basic_connection
- def c.finish; @finished += 1; raise IOError end
- reqs[c.object_id] = 5
+ def (c.http).finish; @finished += 1; raise IOError end
+ c.requests = 5
@http.finish c
- refute c.started?
- assert c.finished?
+ refute c.http.started?
+ assert c.http.finished?
end
def test_http_version
@@ -776,7 +786,6 @@ def test_pipeline
cached = basic_connection
cached.start
- conns['example.com:80'] = cached
requests = [
Net::HTTP::Get.new((@uri + '1').request_uri),
@@ -936,9 +945,38 @@ def test_reconnect
end
def test_reconnect_ssl
- result = @http.reconnect_ssl
+ skip 'OpenSSL is missing' unless HAVE_OPENSSL
- assert_equal 1, result
+ @uri = URI 'https://example.com'
+ now = Time.now
+
+ ssl_http = ssl_connection
+
+ def (ssl_http.http).finish
+ @started = 0
+ end
+
+ used1 = @http.connection_for @uri do |c|
+ c.requests = 1
+ c.last_use = now
+ c
+ end
+
+ assert_equal OpenSSL::SSL::VERIFY_PEER, used1.http.verify_mode
+
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ @http.reconnect_ssl
+
+ used2 = @http.connection_for @uri do |c|
+ c
+ end
+
+ assert_same used1, used2
+
+ assert_equal OpenSSL::SSL::VERIFY_NONE, used2.http.verify_mode,
+ 'verify mode must change'
+ assert_equal 0, used2.requests
+ assert_equal Net::HTTP::Persistent::EPOCH, used2.last_use
end
def test_request
@@ -947,7 +985,7 @@ def test_request
c = connection
res = @http.request @uri
- req = c.req
+ req = c.http.req
assert_kind_of Net::HTTPResponse, res
@@ -960,39 +998,39 @@ def test_request
assert_equal 'keep-alive', req['connection']
assert_equal '30', req['keep-alive']
- assert_in_delta Time.now, touts[c.object_id]
+ assert_in_delta Time.now, c.last_use
- assert_equal 1, reqs[c.object_id]
+ assert_equal 1, c.requests
end
def test_request_ETIMEDOUT
c = basic_connection
- def c.request(*a) raise Errno::ETIMEDOUT, "timed out" end
+ def (c.http).request(*a) raise Errno::ETIMEDOUT, "timed out" end
e = assert_raises Net::HTTP::Persistent::Error do
@http.request @uri
end
- assert_equal 0, reqs[c.object_id]
+ assert_equal 0, c.requests
assert_match %r%too many connection resets%, e.message
end
def test_request_bad_response
c = basic_connection
- def c.request(*a) raise Net::HTTPBadResponse end
+ def (c.http).request(*a) raise Net::HTTPBadResponse end
e = assert_raises Net::HTTP::Persistent::Error do
@http.request @uri
end
- assert_equal 0, reqs[c.object_id]
+ assert_equal 0, c.requests
assert_match %r%too many bad responses%, e.message
end
if RUBY_1 then
def test_request_bad_response_retry
c = basic_connection
- def c.request(*a)
+ def (c.http).request(*a)
if defined? @called then
r = Net::HTTPResponse.allocate
r.instance_variable_set :@header, {}
@@ -1006,26 +1044,26 @@ def r.http_version() '1.1' end
@http.request @uri
- assert c.finished?
+ assert c.http.finished?
end
else
def test_request_bad_response_retry
c = basic_connection
- def c.request(*a)
+ def (c.http).request(*a)
raise Net::HTTPBadResponse
end
assert_raises Net::HTTP::Persistent::Error do
@http.request @uri
end
- assert c.finished?
+ assert c.http.finished?
end
end
def test_request_bad_response_unsafe
c = basic_connection
- def c.request(*a)
+ def (c.http).request(*a)
if instance_variable_defined? :@request then
raise 'POST must not be retried'
else
@@ -1038,7 +1076,7 @@ def c.request(*a)
@http.request @uri, Net::HTTP::Post.new(@uri.path)
end
- assert_equal 0, reqs[c.object_id]
+ assert_equal 0, c.requests
assert_match %r%too many bad responses%, e.message
end
@@ -1051,7 +1089,7 @@ def test_request_block
body = r.read_body
end
- req = c.req
+ req = c.http.req
assert_kind_of Net::HTTPResponse, res
refute_nil body
@@ -1062,17 +1100,17 @@ def test_request_block
assert_equal '30', req['keep-alive']
assert_match %r%test ua%, req['user-agent']
- assert_equal 1, reqs[c.object_id]
+ assert_equal 1, c.requests
end
def test_request_close_1_0
c = connection
- class << c
+ class << c.http
remove_method :request
end
- def c.request req
+ def (c.http).request req
@req = req
r = Net::HTTPResponse.allocate
r.instance_variable_set :@header, {}
@@ -1085,7 +1123,7 @@ def r.read_body() :read_body end
request = Net::HTTP::Get.new @uri.request_uri
res = @http.request @uri, request
- req = c.req
+ req = c.http.req
assert_kind_of Net::HTTPResponse, res
@@ -1094,7 +1132,7 @@ def r.read_body() :read_body end
assert_equal 'keep-alive', req['connection']
assert_equal '30', req['keep-alive']
- assert c.finished?
+ assert c.http.finished?
end
def test_request_connection_close_request
@@ -1104,7 +1142,7 @@ def test_request_connection_close_request
request['connection'] = 'close'
res = @http.request @uri, request
- req = c.req
+ req = c.http.req
assert_kind_of Net::HTTPResponse, res
@@ -1113,17 +1151,17 @@ def test_request_connection_close_request
assert_equal 'close', req['connection']
assert_equal nil, req['keep-alive']
- assert c.finished?
+ assert c.http.finished?
end
def test_request_connection_close_response
c = connection
- class << c
+ class << c.http
remove_method :request
end
- def c.request req
+ def (c.http).request req
@req = req
r = Net::HTTPResponse.allocate
r.instance_variable_set :@header, {}
@@ -1137,7 +1175,7 @@ def r.read_body() :read_body end
request = Net::HTTP::Get.new @uri.request_uri
res = @http.request @uri, request
- req = c.req
+ req = c.http.req
assert_kind_of Net::HTTPResponse, res
@@ -1146,38 +1184,40 @@ def r.read_body() :read_body end
assert_equal 'keep-alive', req['connection']
assert_equal '30', req['keep-alive']
- assert c.finished?
+ assert c.http.finished?
end
def test_request_exception
c = basic_connection
- def c.request(*a) raise Exception, "very bad things happened" end
+ def (c.http).request(*a)
+ raise Exception, "very bad things happened"
+ end
assert_raises Exception do
@http.request @uri
end
- assert_equal 0, reqs[c.object_id]
- assert c.finished?
+ assert_equal 0, c.requests
+ assert c.http.finished?
end
def test_request_invalid
c = basic_connection
- def c.request(*a) raise Errno::EINVAL, "write" end
+ def (c.http).request(*a) raise Errno::EINVAL, "write" end
e = assert_raises Net::HTTP::Persistent::Error do
@http.request @uri
end
- assert_equal 0, reqs[c.object_id]
+ assert_equal 0, c.requests
assert_match %r%too many connection resets%, e.message
end
def test_request_invalid_retry
c = basic_connection
- touts[c.object_id] = Time.now
+ c.last_use = Time.now
- def c.request(*a)
+ def (c.http).request(*a)
if defined? @called then
r = Net::HTTPResponse.allocate
r.instance_variable_set :@header, {}
@@ -1191,8 +1231,8 @@ def r.http_version() '1.1' end
@http.request @uri
- assert c.reset?
- assert c.finished?
+ assert c.http.reset?
+ assert c.http.finished?
end
def test_request_post
@@ -1201,28 +1241,28 @@ def test_request_post
post = Net::HTTP::Post.new @uri.path
@http.request @uri, post
- req = c.req
+ req = c.http.req
assert_same post, req
end
def test_request_reset
c = basic_connection
- def c.request(*a) raise Errno::ECONNRESET end
+ def (c.http).request(*a) raise Errno::ECONNRESET end
e = assert_raises Net::HTTP::Persistent::Error do
@http.request @uri
end
- assert_equal 0, reqs[c.object_id]
+ assert_equal 0, c.requests
assert_match %r%too many connection resets%, e.message
end
if RUBY_1 then
def test_request_reset_retry
c = basic_connection
- touts[c.object_id] = Time.now
- def c.request(*a)
+ c.last_use = Time.now
+ def (c.http).request(*a)
if defined? @called then
r = Net::HTTPResponse.allocate
r.instance_variable_set :@header, {}
@@ -1236,30 +1276,30 @@ def r.http_version() '1.1' end
@http.request @uri
- assert c.reset?
- assert c.finished?
+ assert c.http.reset?
+ assert c.http.finished?
end
else
def test_request_reset_retry
c = basic_connection
- touts[c.object_id] = Time.now
+ c.last_use = Time.now
- def c.request(*a)
+ def (c.http).request(*a)
raise Errno::ECONNRESET
end
assert_raises Net::HTTP::Persistent::Error do
@http.request @uri
end
- refute c.reset?
- assert c.finished?
+ refute (c.http).reset?
+ assert (c.http).finished?
end
end
def test_request_reset_unsafe
c = basic_connection
- def c.request(*a)
+ def (c.http).request(*a)
if instance_variable_defined? :@request then
raise 'POST must not be retried'
else
@@ -1272,23 +1312,24 @@ def c.request(*a)
@http.request @uri, Net::HTTP::Post.new(@uri.path)
end
- assert_equal 0, reqs[c.object_id]
+ assert_equal 0, c.requests
assert_match %r%too many connection resets%, e.message
end
def test_request_ssl_error
skip 'OpenSSL is missing' unless HAVE_OPENSSL
uri = URI.parse 'https://example.com/path'
- c = @http.connection_for uri
- def c.request(*)
- raise OpenSSL::SSL::SSLError, "SSL3_WRITE_PENDING:bad write retry"
- end
+ @http.connection_for uri do |c|
+ def (c.http).request(*)
+ raise OpenSSL::SSL::SSLError, "SSL3_WRITE_PENDING:bad write retry"
+ end
- e = assert_raises Net::HTTP::Persistent::Error do
- @http.request uri
+ e = assert_raises Net::HTTP::Persistent::Error do
+ @http.request uri
+ end
+ assert_match %r%bad write retry%, e.message
end
- assert_match %r%bad write retry%, e.message
end
def test_request_setup
@@ -1320,8 +1361,8 @@ def test_request_setup_uri
def test_request_failed
c = basic_connection
- reqs[c.object_id] = 1
- touts[c.object_id] = Time.now
+ c.requests = 1
+ c.last_use = Time.now
original = nil
@@ -1331,7 +1372,7 @@ def test_request_failed
end
req = Net::HTTP::Get.new '/'
-
+
e = assert_raises Net::HTTP::Persistent::Error do
@http.request_failed original, req, c
end
@@ -1344,24 +1385,24 @@ def test_request_failed
def test_reset
c = basic_connection
- c.start
- touts[c.object_id] = Time.now
- reqs[c.object_id] = 5
+ c.http.start
+ c.last_use = Time.now
+ c.requests = 5
@http.reset c
- assert c.started?
- assert c.finished?
- assert c.reset?
- assert_equal 0, reqs[c.object_id]
- assert_equal Net::HTTP::Persistent::EPOCH, touts[c.object_id]
+ assert c.http.started?
+ assert c.http.finished?
+ assert c.http.reset?
+ assert_equal 0, c.requests
+ assert_equal Net::HTTP::Persistent::EPOCH, c.last_use
end
def test_reset_host_down
c = basic_connection
- touts[c.object_id] = Time.now
- def c.start; raise Errno::EHOSTDOWN end
- reqs[c.object_id] = 5
+ c.last_use = Time.now
+ def (c.http).start; raise Errno::EHOSTDOWN end
+ c.requests = 5
e = assert_raises Net::HTTP::Persistent::Error do
@http.reset c
@@ -1373,20 +1414,20 @@ def c.start; raise Errno::EHOSTDOWN end
def test_reset_io_error
c = basic_connection
- touts[c.object_id] = Time.now
- reqs[c.object_id] = 5
+ c.last_use = Time.now
+ c.requests = 5
@http.reset c
- assert c.started?
- assert c.finished?
+ assert c.http.started?
+ assert c.http.finished?
end
def test_reset_refused
c = basic_connection
- touts[c.object_id] = Time.now
- def c.start; raise Errno::ECONNREFUSED end
- reqs[c.object_id] = 5
+ c.last_use = Time.now
+ def (c.http).start; raise Errno::ECONNREFUSED end
+ c.requests = 5
e = assert_raises Net::HTTP::Persistent::Error do
@http.reset c
@@ -1425,10 +1466,7 @@ def test_retry_change_requests_equals
end
def test_shutdown
- ssl_conns
c = connection
- rs = reqs
- ts = touts
orig = @http
@http = Net::HTTP::Persistent.new 'name'
@@ -1438,120 +1476,8 @@ def test_shutdown
@http = orig
- assert c.finished?, 'last-generation connection must be finished'
- refute c2.finished?, 'present generation connection must not be finished'
-
- refute_same rs, reqs
- refute_same ts, touts
-
- assert_empty conns
- assert_empty ssl_conns
-
- assert_empty reqs
- assert_empty touts
- end
-
- def test_shutdown_in_all_threads
- conns
- ssl_conns
-
- t = Thread.new do
- c = connection
- ssl_conns
- conns
- reqs
-
- Thread.stop
-
- c
- end
-
- Thread.pass until t.status == 'sleep'
-
- c = connection
-
- assert_nil @http.shutdown_in_all_threads
-
- assert c.finished?, 'connection in same thread must be finished'
-
- assert_empty Thread.current[@http.generation_key]
-
- assert_nil Thread.current[@http.request_key]
-
- t.run
- assert t.value.finished?, 'connection in other thread must be finished'
-
- assert_empty t[@http.generation_key]
-
- assert_nil t[@http.request_key]
- end
-
- def test_shutdown_no_connections
- @http.shutdown
-
- assert_nil Thread.current[@http.generation_key]
- assert_nil Thread.current[@http.ssl_generation_key]
-
- assert_nil Thread.current[@http.request_key]
- assert_nil Thread.current[@http.timeout_key]
- end
-
- def test_shutdown_not_started
- ssl_conns
-
- c = basic_connection
- def c.finish() raise IOError end
-
- conns[0]["#{@uri.host}:#{@uri.port}"] = c
-
- @http.shutdown
-
- assert_empty Thread.current[@http.generation_key]
- assert_empty Thread.current[@http.ssl_generation_key]
-
- assert_nil Thread.current[@http.request_key]
- assert_nil Thread.current[@http.timeout_key]
- end
-
- def test_shutdown_ssl
- skip 'OpenSSL is missing' unless HAVE_OPENSSL
-
- @uri = URI 'https://example'
-
- @http.connection_for @uri
-
- @http.shutdown
-
- assert_empty ssl_conns
- end
-
- def test_shutdown_thread
- t = Thread.new do
- c = connection
- conns
- ssl_conns
-
- reqs
-
- Thread.stop
-
- c
- end
-
- Thread.pass until t.status == 'sleep'
-
- c = connection
-
- @http.shutdown t
-
- refute c.finished?
-
- t.run
- assert t.value.finished?
- assert_empty t[@http.generation_key]
- assert_empty t[@http.ssl_generation_key]
- assert_nil t[@http.request_key]
- assert_nil t[@http.timeout_key]
+ assert c.http.finished?, 'last-generation connection must be finished'
+ refute c2.http.finished?, 'present generation connection must not be finished'
end
def test_ssl
@@ -1682,25 +1608,6 @@ def test_ssl_warning
end
end
- def test_ssl_cleanup
- skip 'OpenSSL is missing' unless HAVE_OPENSSL
-
- uri1 = URI.parse 'https://one.example'
-
- c1 = @http.connection_for uri1
-
- touts[c1.object_id] = Time.now
- reqs[c1.object_id] = 5
-
- @http.reconnect_ssl
-
- @http.ssl_cleanup @http.ssl_generation
-
- assert_empty ssl_conns
- assert_empty touts
- assert_empty reqs # sanity check, performed by #finish
- end
-
def test_ssl_timeout_equals
@http.ssl_timeout = :ssl_timeout
@@ -1717,6 +1624,7 @@ def test_ssl_version_equals
def test_start
c = basic_connection
+ c = c.http
@http.socket_options << [Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1]
@http.debug_output = $stderr
View
151 test/test_net_http_persistent_timed_stack_multi.rb
@@ -0,0 +1,151 @@
+require 'minitest/autorun'
+require 'net/http/persistent'
+
+class TestNetHttpPersistentTimedStackMulti < Minitest::Test
+
+ class Connection
+ attr_reader :host
+
+ def initialize(host)
+ @host = host
+ end
+ end
+
+ def setup
+ @stack = Net::HTTP::Persistent::TimedStackMulti.new { Object.new }
+ end
+
+ def test_empty_eh
+ stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new }
+
+ refute_empty stack
+
+ popped = stack.pop
+
+ assert_empty stack
+
+ stack.push connection_args: popped
+
+ refute_empty stack
+ end
+
+ def test_length
+ stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new }
+
+ assert_equal 1, stack.length
+
+ popped = stack.pop
+
+ assert_equal 0, stack.length
+
+ stack.push connection_args: popped
+
+ assert_equal 1, stack.length
+ end
+
+ def test_pop
+ object = Object.new
+ @stack.push object
+
+ popped = @stack.pop
+
+ assert_same object, popped
+ end
+
+ def test_pop_empty
+ e = assert_raises Timeout::Error do
+ @stack.pop timeout: 0
+ end
+
+ assert_equal 'Waited 0 sec', e.message
+ end
+
+ def test_pop_full
+ stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new }
+
+ popped = stack.pop
+
+ refute_nil popped
+ assert_empty stack
+ end
+
+ def test_pop_wait
+ thread = Thread.start do
+ @stack.pop
+ end
+
+ Thread.pass while thread.status == 'run'
+
+ object = Object.new
+
+ @stack.push object
+
+ assert_same object, thread.value
+ end
+
+ def test_pop_shutdown
+ @stack.shutdown { }
+
+ assert_raises ConnectionPool::PoolShuttingDownError do
+ @stack.pop
+ end
+ end
+
+ def test_push
+ stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new }
+
+ conn = stack.pop
+
+ stack.push connection_args: conn
+
+ refute_empty stack
+ end
+
+ def test_push_shutdown
+ called = []
+
+ @stack.shutdown do |object|
+ called << object
+ end
+
+ @stack.push connection_args: Object.new
+
+ refute_empty called
+ assert_empty @stack
+ end
+
+ def test_shutdown
+ @stack.push connection_args: Object.new
+
+ called = []
+
+ @stack.shutdown do |object|
+ called << object
+ end
+
+ refute_empty called
+ assert_empty @stack
+ end
+
+ def test_pop_recycle
+ stack = Net::HTTP::Persistent::TimedStackMulti.new(2) { |host| Connection.new(host) }
+
+ a_conn = stack.pop connection_args: 'a.example'
+ stack.push a_conn, connection_args: 'a.example'
+
+ b_conn = stack.pop connection_args: 'b.example'
+ stack.push b_conn, connection_args: 'b.example'
+
+ c_conn = stack.pop connection_args: 'c.example'
+
+ assert_equal 'c.example', c_conn.host
+
+ stack.push c_conn, connection_args: 'c.example'
+
+ recreated = stack.pop connection_args: 'a.example'
+
+ refute_same a_conn, recreated
+ end
+
+end
+

0 comments on commit 5bae22e

Please sign in to comment.