threading and timeouts

Phil Mayes nospam at
Sat Nov 27 08:12:29 CET 1999

Piers Lauder wrote in message <943504970.076.346131773 at>...
>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 that uses select,
based on code from Lloyd Zusman <ljz at>.  I then take advantage
of and by inheriting:

class timeout_POP3(poplib.POP3):
    def __init__(self, host, port = poplib.POP3_PORT): = host
        self.port = port
        self.sock = timeout_socket.timeout_socket()
        self.sock.connect(, 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)
        if self.debuglevel >0 : print "connect:", msg
        return (code,msg)

This allows me to use & 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):

class Closing(Exception):

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

    # destructor notes: socket.socket will close when destroyed

    def connect(self, host, port):
        timeout = self.timeout
        s = self.s
            # Non-blocking mode
            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
                code,why = why
            if timeout:
                if code in (errno.EINPROGRESS, errno.EALREADY,
                    # Ok, then wait...
                    r,w,e =[],[s],[],timeout)
                    if w:
                            s.connect(host, port)
                            return 1
                        except socket.error,why:
                            # This can throw string or (int,string)
                            if len(why.args) == 1:
                                code = 0
                                code,why = why
                            if code == errno.EISCONN:
                                return 1
            code = 0
            why = 'Unknown error trying to connect'

        # format the error message
        if code:
            code = 'error %d = ' % code
            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 =[],[self.s], [], t)
            if w:
                buf = data[next:next+8192]
                sent = self.s.send(buf)
                next = next + sent
                if next == total:
                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 =[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 [] !=[self.s], [], [], timeout or

    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:
            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:
            self.inbuf = self.inbuf + self.recv(4096)
        data = self.inbuf[:crlf] + '\n'
        self.inbuf = self.inbuf[crlf+2:]
        return data

    def close(self):

    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
        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

More information about the Python-list mailing list