try all IPs of a given host in case first one fails. Fixes #2958

This commit is contained in:
Yann Leboulanger 2008-04-16 11:02:01 +00:00
parent b936fa4bfe
commit 3aefee4700
1 changed files with 58 additions and 48 deletions

View File

@ -274,6 +274,7 @@ class NonBlockingTcp(PlugIn, IdleObject):
# This prevents replug of same object with the same flags # This prevents replug of same object with the same flags
self.writable = True self.writable = True
self.readable = False self.readable = False
self.ais = None
def plugin(self, owner): def plugin(self, owner):
''' Fire up connection. Return non-empty string on success. ''' Fire up connection. Return non-empty string on success.
@ -296,46 +297,34 @@ class NonBlockingTcp(PlugIn, IdleObject):
if self.on_timeout: if self.on_timeout:
self.on_timeout() self.on_timeout()
self.renew_send_timeout() self.renew_send_timeout()
def connect(self,server=None, proxy = None, secure = None): def connect(self,server=None, proxy = None, secure = None):
''' Try to establish connection. Returns True/False on success/failure. ''' ''' Try to establish connection. '''
if not server: if not server:
server=self._server server=self._server
else: else:
self._server = server self._server = server
self.printed_error = False self.printed_error = False
self.state = 0 self.state = 0
success = False
try: try:
for ai in socket.getaddrinfo(server[0],server[1],socket.AF_UNSPEC,socket.SOCK_STREAM): self.set_timeout(CONNECT_TIMEOUT_SECONDS)
try: if len(server) == 2 and type(server[0]) in (str, unicode) and not \
self._sock=socket.socket(*ai[:3]) self.ais:
self._sock.setblocking(False) # FIXME: blocks here
self._server=ai[4] self.ais = socket.getaddrinfo(server[0],server[1],socket.AF_UNSPEC,socket.SOCK_STREAM)
success = True log.info('Found IPs: %s', self.ais)
break else:
except: self.ais = (server,)
if sys.exc_value[0] == errno.EINPROGRESS: self.connect_to_next_ip()
success = True return
break
#for all errors, we try other addresses
continue
except socket.gaierror, e: except socket.gaierror, e:
log.info("Lookup failure for %s: %s[%s]", self.getName(), e[1], repr(e[0]), exc_info=True) log.info('Lookup failure for %s: %s[%s]', self.getName(), e[1], repr(e[0]), exc_info=True)
except: except:
log.error("Exception trying to connect to %s:", self.getName(), exc_info=True) log.error('Exception trying to connect to %s:', self.getName(), exc_info=True)
if not success: if self.on_connect_failure:
if self.on_connect_failure: self.on_connect_failure()
self.on_connect_failure()
return False
self.fd = self._sock.fileno()
self.idlequeue.plug_idle(self, True, False)
self.set_timeout(CONNECT_TIMEOUT_SECONDS)
self._do_connect()
return True
def _plug_idle(self): def _plug_idle(self):
readable = self.state != 0 readable = self.state != 0
if self.sendqueue or self.sendbuff: if self.sendqueue or self.sendbuff:
@ -347,8 +336,9 @@ class NonBlockingTcp(PlugIn, IdleObject):
def pollout(self): def pollout(self):
if self.state == 0: if self.state == 0:
return self._do_connect() self.connect_to_next_ip()
return self._do_send() return
self._do_send()
def plugout(self): def plugout(self):
''' Disconnect from the remote server and unregister self.disconnected method from ''' Disconnect from the remote server and unregister self.disconnected method from
@ -538,19 +528,22 @@ class NonBlockingTcp(PlugIn, IdleObject):
return return
return True return True
def _do_connect(self): def connect_to_next_ip(self):
if self.state != 0: if self.state != 0:
return return
self._sock.setblocking(False) if len(self.ais) == 0:
self._send = self._sock.send if self.on_connect_failure:
self._recv = self._sock.recv self.on_connect_failure()
errnum = 0 return
ai = self.ais.pop(0)
log.info('Trying to connect to %s:%s', ai[4][0], ai[4][1])
try: try:
self._sock.connect(self._server) self._sock = socket.socket(*ai[:3])
self._server=ai[4]
except socket.error, e: except socket.error, e:
errnum = e[0] errnum, errstr = e
# Ignore "Socket already connected". # Ignore "Socket already connected".
# FIXME: This happens when we switch an already # FIXME: This happens when we switch an already
# connected socket to SSL (STARTTLS). Instead of # connected socket to SSL (STARTTLS). Instead of
# ignoring the error, the socket should only be # ignoring the error, the socket should only be
@ -559,28 +552,45 @@ class NonBlockingTcp(PlugIn, IdleObject):
# 10035 - winsock equivalent of EINPROGRESS # 10035 - winsock equivalent of EINPROGRESS
if errnum not in (errno.EINPROGRESS, 10035) + workaround: if errnum not in (errno.EINPROGRESS, 10035) + workaround:
log.error("_do_connect:", exc_info=True) log.error('Could not connect to %s: %s [%s]', ai[4][0], errnum,
errstr, exc_info=True)
#traceback.print_exc() #traceback.print_exc()
self.connect_to_next_ip()
return
self.fd = self._sock.fileno()
self.idlequeue.plug_idle(self, True, False)
self._send = self._sock.send
self._recv = self._sock.recv
self._do_connect()
def _do_connect(self):
errnum = 0
try:
self._sock.connect(self._server)
self._sock.setblocking(False)
except Exception, ee:
(errnum, errstr) = ee
# in progress, or would block # in progress, or would block
if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK): if errnum in (errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK):
self.state = 1
return return
# 10056 - already connected, only on win32 # 10056 - already connected, only on win32
# code 'WS*' is not available on GNU, so we use its numeric value # code 'WS*' is not available on GNU, so we use its numeric value
elif errnum not in (0, 10056, errno.EISCONN): elif errnum not in (0, 10056, errno.EISCONN):
self.remove_timeout() log.error('Could not connect to %s: %s [%s]', self._server[0], errnum,
if self.on_connect_failure: errstr)
self.on_connect_failure() self.connect_to_next_ip()
return return
self.remove_timeout() self.remove_timeout()
self._owner.Connection=self self._owner.Connection=self
self.state = 1 self.state = 1
self._sock.setblocking(False) self._sock.setblocking(False)
self._plug_idle() self._plug_idle()
if self.on_connect: if self.on_connect:
self.on_connect() self.on_connect()
self.on_connect = None self.on_connect = None
return True
def send(self, raw_data, now = False): def send(self, raw_data, now = False):
'''Append raw_data to the queue of messages to be send. '''Append raw_data to the queue of messages to be send.