RUBY-416 threading with refresh test fixes

This commit is contained in:
Tyler Brock 2012-03-02 19:27:52 -05:00
parent bf9bb83b6d
commit 274ce690e7
3 changed files with 127 additions and 104 deletions

View File

@ -114,6 +114,7 @@ module Mongo
# No connection manager by default. # No connection manager by default.
@manager = nil @manager = nil
@old_managers = []
# Lock for request ids. # Lock for request ids.
@id_lock = Mutex.new @id_lock = Mutex.new
@ -125,6 +126,7 @@ module Mongo
@safe_mutexes = Hash.new {|hash, key| hash[key] = Mutex.new} @safe_mutexes = Hash.new {|hash, key| hash[key] = Mutex.new}
@connect_mutex = Mutex.new @connect_mutex = Mutex.new
@refresh_mutex = Mutex.new
check_opts(opts) check_opts(opts)
setup(opts) setup(opts)
@ -147,14 +149,15 @@ module Mongo
discovered_seeds = @manager ? @manager.seeds : [] discovered_seeds = @manager ? @manager.seeds : []
@manager = PoolManager.new(self, discovered_seeds) @manager = PoolManager.new(self, discovered_seeds)
Thread.current[:manager] = @manager
@manager.connect @manager.connect
@refresh_version += 1 @refresh_version += 1
if @require_primary && self.primary.nil? #TODO: in v2.0, we'll let this be optional and do a lazy connect. if @require_primary && @manager.primary.nil? #TODO: in v2.0, we'll let this be optional and do a lazy connect.
close close
raise ConnectionFailure, "Failed to connect to primary node." raise ConnectionFailure, "Failed to connect to primary node."
elsif self.read_pool.nil? elsif @manager.read_pool.nil?
close close
raise ConnectionFailure, "Failed to connect to any node." raise ConnectionFailure, "Failed to connect to any node."
else else
@ -196,19 +199,19 @@ module Mongo
def hard_refresh! def hard_refresh!
log(:info, "Initiating hard refresh...") log(:info, "Initiating hard refresh...")
discovered_seeds = @manager ? @manager.seeds : [] discovered_seeds = @manager ? @manager.seeds : []
background_manager = PoolManager.new(self, discovered_seeds | @seeds) new_manager = PoolManager.new(self, discovered_seeds | @seeds)
background_manager.connect new_manager.connect
# TODO: make sure that connect has succeeded # TODO: make sure that connect has succeeded
old_manager = @manager @old_managers << @manager
@manager = background_manager @manager = new_manager
old_manager.close(:soft => true)
@refresh_version += 1 @refresh_version += 1
return true return true
end end
def connected? def connected?
@connected && (self.primary_pool || self.read_pool) @connected && (@manager.primary_pool || @manager.read_pool)
end end
# @deprecated # @deprecated
@ -221,14 +224,14 @@ module Mongo
# #
# @return [String] # @return [String]
def host def host
self.primary_pool.host @manager.primary_pool.host
end end
# The replica set primary's port. # The replica set primary's port.
# #
# @return [Integer] # @return [Integer]
def port def port
self.primary_pool.port @manager.primary_pool.port
end end
def nodes def nodes
@ -242,7 +245,7 @@ module Mongo
# #
# @return [Boolean] # @return [Boolean]
def read_primary? def read_primary?
self.read_pool == self.primary_pool @manager.read_pool == @manager.primary_pool
end end
alias :primary? :read_primary? alias :primary? :read_primary?
@ -294,96 +297,68 @@ module Mongo
end end
end end
# Generic socket checkout
# Takes a block that returns a socket from pool
def checkout(&block)
if connected?
sync_refresh
else
connect
end
begin
socket = block.call
rescue => ex
checkin(socket) if socket
raise ex
end
if socket
socket
else
@connected = false
raise ConnectionFailure.new("Could not checkout a socket")
end
end
# Checkout a socket for reading (i.e., a secondary node). # Checkout a socket for reading (i.e., a secondary node).
# Note that @read_pool might point to the primary pool # Note that @read_pool might point to the primary pool
# if no read pool has been defined. # if no read pool has been defined.
def checkout_reader def checkout_reader
if connected? checkout do
sync_refresh socket = get_socket_from_pool(:read)
else
connect
end
begin
socket = get_socket_from_pool(self.read_pool)
if !socket if !socket
connect connect
socket = get_socket_from_pool(self.primary_pool) socket = get_socket_from_pool(:primary)
end end
rescue => ex
checkin(socket) if socket
raise ex
end
if socket
socket socket
else
@connected = false
raise ConnectionFailure.new("Could not connect to a node for reading.")
end end
end end
# Checkout a socket from a secondary
# For :read_preference => :secondary_only
def checkout_secondary def checkout_secondary
if connected? checkout do
sync_refresh get_socket_from_pool(:secondary)
else
connect
end
begin
socket = get_socket_from_pool(self.secondary_pool)
rescue => ex
checkin(socket) if socket
raise ex
end
if socket
socket
else
@connected = false
raise ConnectionFailure.new("Could not connect to a secondary for reading.")
end end
end end
# Checkout a socket for writing (i.e., a primary node). # Checkout a socket for writing (i.e., a primary node).
def checkout_writer def checkout_writer
if connected? checkout do
sync_refresh get_socket_from_pool(:primary)
else
connect
end
begin
socket = get_socket_from_pool(self.primary_pool)
if !socket
connect
socket = get_socket_from_pool(self.primary_pool)
end
rescue => ex
checkin(socket)
raise ex
end
if socket
socket
else
@connected = false
raise ConnectionFailure.new("Could not connect to primary node.")
end end
end end
# Checkin a socket used for reading. # Checkin a socket used for reading.
def checkin_reader(socket) def checkin_reader(socket)
if !((self.read_pool && self.read_pool.checkin(socket)) || socket.pool.checkin(socket)
(self.primary_pool && self.primary_pool.checkin(socket)))
close_socket(socket)
end
sync_refresh sync_refresh
end end
# Checkin a socket used for writing. # Checkin a socket used for writing.
def checkin_writer(socket) def checkin_writer(socket)
if !self.primary_pool || !self.primary_pool.checkin(socket) socket.pool.checkin(socket)
close_socket(socket)
end
sync_refresh sync_refresh
end end
@ -395,7 +370,20 @@ module Mongo
end end
end end
def get_socket_from_pool(pool) def get_socket_from_pool(pool_type)
if Thread.current[:manager] != @manager
Thread.current[:manager] = @manager
end
pool = case pool_type
when :primary
primary_pool
when :secondary
secondary_pool
when :read
read_pool
end
begin begin
if pool if pool
socket = pool.checkout socket = pool.checkout
@ -407,46 +395,50 @@ module Mongo
end end
end end
def local_manager
Thread.current[:manager]
end
def arbiters def arbiters
@manager.arbiters.nil? ? [] : @manager.arbiters local_manager.arbiters.nil? ? [] : local_manager.arbiters
end end
def primary def primary
@manager ? @manager.primary : nil local_manager ? local_manager.primary : nil
end end
# Note: might want to freeze these after connecting. # Note: might want to freeze these after connecting.
def secondaries def secondaries
@manager ? @manager.secondaries : [] local_manager ? local_manager.secondaries : []
end end
def hosts def hosts
@manager ? @manager.hosts : [] local_manager ? local_manager.hosts : []
end end
def primary_pool def primary_pool
@manager ? @manager.primary_pool : nil local_manager ? local_manager.primary_pool : nil
end end
def read_pool def read_pool
@manager ? @manager.read_pool : nil local_manager ? local_manager.read_pool : nil
end end
def secondary_pool def secondary_pool
@manager ? @manager.secondary_pool : nil local_manager ? local_manager.secondary_pool : nil
end end
def secondary_pools def secondary_pools
@manager ? @manager.secondary_pools : [] local_manager ? local_manager.secondary_pools : []
end end
def tag_map def tag_map
@manager ? @manager.tag_map : {} local_manager ? local_manager.tag_map : {}
end end
def max_bson_size def max_bson_size
if @manager && @manager.max_bson_size if local_manager && local_manager.max_bson_size
@manager.max_bson_size local_manager.max_bson_size
else else
Mongo::DEFAULT_MAX_BSON_SIZE Mongo::DEFAULT_MAX_BSON_SIZE
end end
@ -514,11 +506,31 @@ module Mongo
"Could not find a connection tagged with #{tags}." "Could not find a connection tagged with #{tags}."
end end
def prune_managers
@old_managers.each do |manager|
if manager != @manager
if manager.closed?
@old_managers.delete(manager)
else
manager.close(:soft => true)
end
end
end
end
def sync_refresh def sync_refresh
if @refresh_mode == :sync && if @refresh_mode == :sync &&
((Time.now - @last_refresh) > @refresh_interval) ((Time.now - @last_refresh) > @refresh_interval)
@last_refresh = Time.now @last_refresh = Time.now
if @refresh_mutex.try_lock
begin
prune_managers
refresh refresh
ensure
@refresh_mutex.unlock
end
end
end end
end end
end end

View File

@ -65,24 +65,14 @@ module Mongo
# close only those sockets that are not checked out. # close only those sockets that are not checked out.
def close(opts={}) def close(opts={})
@connection_mutex.synchronize do @connection_mutex.synchronize do
if opts[:soft] if opts[:soft] && !@checked_out.empty?
sockets_to_close = @sockets - @checked_out close_sockets(@sockets - @checked_out)
else else
sockets_to_close = @sockets close_sockets(@sockets)
end
sockets_to_close.each do |sock|
begin
sock.close unless sock.closed?
rescue IOError => ex
warn "IOError when attempting to close socket connected to #{@host}:#{@port}: #{ex.inspect}"
end
end
@sockets.clear
@pids.clear
@checked_out.clear
@closed = true @closed = true
end end
end end
end
def closed? def closed?
@closed @closed
@ -300,5 +290,18 @@ module Mongo
end end
end end
end end
private
def close_sockets(sockets)
sockets.each do |socket|
begin
socket.close unless socket.closed?
rescue IOError => ex
warn "IOError when attempting to close socket connected to #{@host}:#{@port}: #{ex.inspect}"
end
end
end
end end
end end

View File

@ -84,6 +84,10 @@ module Mongo
@refresh_required @refresh_required
end end
def closed?
pools.all? { |pool| pool.closed? }
end
def close(opts={}) def close(opts={})
begin begin
if @primary_pool if @primary_pool
@ -114,6 +118,10 @@ module Mongo
private private
def pools
[@primary_pool, *@secondary_pools]
end
def validate_existing_member(member) def validate_existing_member(member)
config = member.set_config config = member.set_config
if !config if !config