threading and timeouts
Phil Mayes
nospam at bitbucket.com
Sat Nov 27 02:12:29 EST 1999
Piers Lauder wrote in message <943504970.076.346131773 at cs.usyd.edu.au>...
>Are timeouts possible inside threads?
>
>Eg: escaping from an attempt to use smtplib.SMTP().sendmail() inside a
>child thread if the remote host is being extremely slow.
>
>It doesn't seem possible to use the traditional signal(SIG_ALRM,...)
>approach because signals only work inside the main thread.
>
>The only option I can think of is to re-write the smtplib module to use
>`select', but does anyone know of a simpler solution?
I struggled with this for some time, failed to get plain old timeouts
working, and ended up with a timeout_socket.py that uses select,
based on code from Lloyd Zusman <ljz at asfast.com>. I then take advantage
of smtplib.py and poplib.py by inheriting:
class timeout_POP3(poplib.POP3):
def __init__(self, host, port = poplib.POP3_PORT):
self.host = host
self.port = port
self.sock = timeout_socket.timeout_socket()
self.sock.connect(self.host, self.port)
self.file = self.sock.makefile('rb') # same as poplib.POP3
self._debugging = 0
self.welcome = self._getresp()
class timeout_SMTP(smtplib.SMTP):
def connect(self, host='localhost', port = 0):
"""Connect to a host on a given port.
Override the std SMTP connect in order to use a timeout_socket.
"""
if not port:
i = string.find(host, ':')
if i >= 0:
host, port = host[:i], host[i+1:]
try: port = string.atoi(port)
except string.atoi_error:
raise socket.error, "nonnumeric port"
if not port: port = smtplib.SMTP_PORT
self.sock = timeout_socket.timeout_socket()
if self.debuglevel > 0: print 'connect:', (host, port)
self.sock.connect(host, port)
(code,msg)=self.getreply()
if self.debuglevel >0 : print "connect:", msg
return (code,msg)
This allows me to use poplib.py & smtplib.py unchanged.
The timeout_socket class follows. I run on Win9x and have a vague
feeling that this won't run on Unix which is why I didn't publish it
before. If you find out why, yell!
------------ code start ----------------------
""" timeout socket class for connections that can potentially cause
the server to hang
"""
import socket
import errno
import select
import string
#import appstate # see comment below
_TIMEOUT = 20.0
class Timeout(Exception):
pass
class Closing(Exception):
pass
class timeout_socket:
def __init__(self, timeout=_TIMEOUT, s=None):
self.timeout = timeout
self.inbuf = ''
if s == None:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.s = s
self.s.setblocking(0)
# destructor notes: socket.socket will close when destroyed
def connect(self, host, port):
timeout = self.timeout
s = self.s
try:
# Non-blocking mode
s.setblocking(0)
s.connect(host,port)
s.setblocking(timeout != 0)
return 1
except socket.error,why:
# The exception arguments can be (string) or (int,string)
if len(why.args) == 1:
code = 0
else:
code,why = why
if timeout:
s.setblocking(1)
if code in (errno.EINPROGRESS, errno.EALREADY,
errno.EWOULDBLOCK):
# Ok, then wait...
r,w,e = select.select([],[s],[],timeout)
if w:
try:
s.connect(host, port)
return 1
except socket.error,why:
# This can throw string or (int,string)
if len(why.args) == 1:
code = 0
else:
code,why = why
if code == errno.EISCONN:
return 1
except:
code = 0
why = 'Unknown error trying to connect'
# format the error message
if code:
code = 'error %d = ' % code
else:
code = ''
msg = 'Connect to %s timed out after %d sec (%s%s)' % (host,
int(self.timeout), code, why)
raise Timeout, msg
def send(self, data, timeout=0):
next = 0
t = timeout or self.timeout
total = len(data)
while 1:
if appstate.state > appstate.running:
raise Closing, 'closing'
r,w,e = select.select([],[self.s], [], t)
if w:
buf = data[next:next+8192]
sent = self.s.send(buf)
next = next + sent
if next == total:
return
else:
raise Timeout, 'timeout while sending "%.20s...": %d sec' %
(data, int(t))
def recv(self, amt, timeout=0):
t = timeout or self.timeout
r,w,e = select.select([self.s], [], [], t)
if r:
recvd = self.s.recv(amt)
return recvd
if not hasattr(self,'s'): print 'timeout_socket has no socket s
?!',self.s #TMP - Sam's problem 9/21
raise Timeout, "timeout while receiving from %s: %d sec" %
(`self.s.getpeername()`, int(timeout))
def recvpending(self, timeout=0):
""" returns 1/0 """
return [] != select.select([self.s], [], [], timeout or
self.timeout)[0]
def read(self, amt):
""" This only returns when amt has been read or socket times out """
# new version works with readline (except this doesn't convert CRLF
=> LF)
while 1:
if appstate.state > appstate.running:
raise Closing, 'closing'
if len(self.inbuf) >= amt:
break
self.inbuf = self.inbuf + self.recv(4096)
data = self.inbuf[:amt]
self.inbuf = self.inbuf[amt:]
return data
# new readline buffers data
# this -isn't- called for the makefile clone
def readline(self):
while 1:
# useful but non-generic code:
# if appstate.state > appstate.running:
# raise Closing, 'closing'
crlf = string.find(self.inbuf, '\r\n')
if crlf >= 0:
break
self.inbuf = self.inbuf + self.recv(4096)
data = self.inbuf[:crlf] + '\n'
self.inbuf = self.inbuf[crlf+2:]
return data
def close(self):
self.s.close()
def makefile(self, flags):
self.s.setblocking(1) # s is a socket._socketobject
save = self.s._sock # stash the real thing
self.s._sock = self # replace it by us
# this call creates a _fileobject that has been passed self as the
socket
x = self.s.makefile(flags)
self.s._sock = save # restore the real socket
return x
------------ code end ------------------------
--
Phil Mayes pmayes AT olivebr DOT com
Olive Branch Software - home of Arranger
http://www.olivebr.com/
More information about the Python-list
mailing list