From b81843ac2016d40d789bf147f41b30e76e331ff9 Mon Sep 17 00:00:00 2001 From: Adam Sampson Date: Sat, 3 May 2003 12:56:42 +0000 Subject: [PATCH] Add timeoutsocket. --- rawdoglib/__init__.py | 2 +- rawdoglib/timeoutsocket.py | 424 +++++++++++++++++++++++++++++++++++++ 2 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 rawdoglib/timeoutsocket.py diff --git a/rawdoglib/__init__.py b/rawdoglib/__init__.py index c4c55cf..7e91484 100644 --- a/rawdoglib/__init__.py +++ b/rawdoglib/__init__.py @@ -1 +1 @@ -__all__ = ['rssparser'] +__all__ = ['rssparser', 'timeoutsocket'] diff --git a/rawdoglib/timeoutsocket.py b/rawdoglib/timeoutsocket.py new file mode 100644 index 0000000..2246419 --- /dev/null +++ b/rawdoglib/timeoutsocket.py @@ -0,0 +1,424 @@ + +#### +# Copyright 2000,2001 by Timothy O'Malley +# +# All Rights Reserved +# +# Permission to use, copy, modify, and distribute this software +# and its documentation for any purpose and without fee is hereby +# granted, provided that the above copyright notice appear in all +# copies and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Timothy O'Malley not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# Timothy O'Malley DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +# SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS, IN NO EVENT SHALL Timothy O'Malley BE LIABLE FOR +# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. +# +#### + +"""Timeout Socket + +This module enables a timeout mechanism on all TCP connections. It +does this by inserting a shim into the socket module. After this module +has been imported, all socket creation goes through this shim. As a +result, every TCP connection will support a timeout. + +The beauty of this method is that it immediately and transparently +enables the entire python library to support timeouts on TCP sockets. +As an example, if you wanted to SMTP connections to have a 20 second +timeout: + + import timeoutsocket + import smtplib + timeoutsocket.setDefaultSocketTimeout(20) + + +The timeout applies to the socket functions that normally block on +execution: read, write, connect, and accept. If any of these +operations exceeds the specified timeout, the exception Timeout +will be raised. + +The default timeout value is set to None. As a result, importing +this module does not change the default behavior of a socket. The +timeout mechanism only activates when the timeout has been set to +a numeric value. (This behavior mimics the behavior of the +select.select() function.) + +This module implements two classes: TimeoutSocket and TimeoutFile. + +The TimeoutSocket class defines a socket-like object that attempts to +avoid the condition where a socket may block indefinitely. The +TimeoutSocket class raises a Timeout exception whenever the +current operation delays too long. + +The TimeoutFile class defines a file-like object that uses the TimeoutSocket +class. When the makefile() method of TimeoutSocket is called, it returns +an instance of a TimeoutFile. + +Each of these objects adds two methods to manage the timeout value: + + get_timeout() --> returns the timeout of the socket or file + set_timeout() --> sets the timeout of the socket or file + + +As an example, one might use the timeout feature to create httplib +connections that will timeout after 30 seconds: + + import timeoutsocket + import httplib + H = httplib.HTTP("www.python.org") + H.sock.set_timeout(30) + +Note: When used in this manner, the connect() routine may still +block because it happens before the timeout is set. To avoid +this, use the 'timeoutsocket.setDefaultSocketTimeout()' function. + +Good Luck! + +""" + +__version__ = "$Revision: 1.1 $" +__author__ = "Timothy O'Malley " + +# +# Imports +# +import select, string +import socket +if not hasattr(socket, "_no_timeoutsocket"): + _socket = socket.socket +else: + _socket = socket._no_timeoutsocket + + +# +# Set up constants to test for Connected and Blocking operations. +# We delete 'os' and 'errno' to keep our namespace clean(er). +# Thanks to Alex Martelli and G. Li for the Windows error codes. +# +import os +if os.name == "nt": + _IsConnected = ( 10022, 10056 ) + _ConnectBusy = ( 10035, ) + _AcceptBusy = ( 10035, ) +else: + import errno + _IsConnected = ( errno.EISCONN, ) + _ConnectBusy = ( errno.EINPROGRESS, errno.EALREADY, errno.EWOULDBLOCK ) + _AcceptBusy = ( errno.EAGAIN, errno.EWOULDBLOCK ) + del errno +del os + + +# +# Default timeout value for ALL TimeoutSockets +# +_DefaultTimeout = None +def setDefaultSocketTimeout(timeout): + global _DefaultTimeout + _DefaultTimeout = timeout +def getDefaultSocketTimeout(): + return _DefaultTimeout + +# +# Exceptions for socket errors and timeouts +# +Error = socket.error +class Timeout(Exception): + pass + + +# +# Factory function +# +from socket import AF_INET, SOCK_STREAM +def timeoutsocket(family=AF_INET, type=SOCK_STREAM, proto=None): + if family != AF_INET or type != SOCK_STREAM: + if proto: + return _socket(family, type, proto) + else: + return _socket(family, type) + return TimeoutSocket( _socket(family, type), _DefaultTimeout ) +# end timeoutsocket + +# +# The TimeoutSocket class definition +# +class TimeoutSocket: + """TimeoutSocket object + Implements a socket-like object that raises Timeout whenever + an operation takes too long. + The definition of 'too long' can be changed using the + set_timeout() method. + """ + + _copies = 0 + _blocking = 1 + + def __init__(self, sock, timeout): + self._sock = sock + self._timeout = timeout + # end __init__ + + def __getattr__(self, key): + return getattr(self._sock, key) + # end __getattr__ + + def get_timeout(self): + return self._timeout + # end set_timeout + + def set_timeout(self, timeout=None): + self._timeout = timeout + # end set_timeout + + def setblocking(self, blocking): + self._blocking = blocking + return self._sock.setblocking(blocking) + # end set_timeout + + def connect_ex(self, addr): + errcode = 0 + try: + self.connect(addr) + except Error, why: + errcode = why[0] + return errcode + # end connect_ex + + def connect(self, addr, port=None, dumbhack=None): + # In case we were called as connect(host, port) + if port != None: addr = (addr, port) + + # Shortcuts + sock = self._sock + timeout = self._timeout + blocking = self._blocking + + # First, make a non-blocking call to connect + try: + sock.setblocking(0) + sock.connect(addr) + sock.setblocking(blocking) + return + except Error, why: + # Set the socket's blocking mode back + sock.setblocking(blocking) + + # If we are not blocking, re-raise + if not blocking: + raise + + # If we are already connected, then return success. + # If we got a genuine error, re-raise it. + errcode = why[0] + if dumbhack and errcode in _IsConnected: + return + elif errcode not in _ConnectBusy: + raise + + # Now, wait for the connect to happen + # ONLY if dumbhack indicates this is pass number one. + # If select raises an error, we pass it on. + # Is this the right behavior? + if not dumbhack: + r,w,e = select.select([], [sock], [], timeout) + if w: + return self.connect(addr, dumbhack=1) + + # If we get here, then we should raise Timeout + raise Timeout("Attempted connect to %s timed out." % str(addr) ) + # end connect + + def accept(self, dumbhack=None): + # Shortcuts + sock = self._sock + timeout = self._timeout + blocking = self._blocking + + # First, make a non-blocking call to accept + # If we get a valid result, then convert the + # accept'ed socket into a TimeoutSocket. + # Be carefult about the blocking mode of ourselves. + try: + sock.setblocking(0) + newsock, addr = sock.accept() + sock.setblocking(blocking) + timeoutnewsock = self.__class__(newsock, timeout) + timeoutnewsock.setblocking(blocking) + return (timeoutnewsock, addr) + except Error, why: + # Set the socket's blocking mode back + sock.setblocking(blocking) + + # If we are not supposed to block, then re-raise + if not blocking: + raise + + # If we got a genuine error, re-raise it. + errcode = why[0] + if errcode not in _AcceptBusy: + raise + + # Now, wait for the accept to happen + # ONLY if dumbhack indicates this is pass number one. + # If select raises an error, we pass it on. + # Is this the right behavior? + if not dumbhack: + r,w,e = select.select([sock], [], [], timeout) + if r: + return self.accept(dumbhack=1) + + # If we get here, then we should raise Timeout + raise Timeout("Attempted accept timed out.") + # end accept + + def send(self, data, flags=0): + sock = self._sock + if self._blocking: + r,w,e = select.select([],[sock],[], self._timeout) + if not w: + raise Timeout("Send timed out") + return sock.send(data, flags) + # end send + + def recv(self, bufsize, flags=0): + sock = self._sock + if self._blocking: + r,w,e = select.select([sock], [], [], self._timeout) + if not r: + raise Timeout("Recv timed out") + return sock.recv(bufsize, flags) + # end recv + + def makefile(self, flags="r", bufsize=-1): + self._copies = self._copies +1 + return TimeoutFile(self, flags, bufsize) + # end makefile + + def close(self): + if self._copies <= 0: + self._sock.close() + else: + self._copies = self._copies -1 + # end close + +# end TimeoutSocket + + +class TimeoutFile: + """TimeoutFile object + Implements a file-like object on top of TimeoutSocket. + """ + + def __init__(self, sock, mode="r", bufsize=4096): + self._sock = sock + self._bufsize = 4096 + if bufsize > 0: self._bufsize = bufsize + if not hasattr(sock, "_inqueue"): self._sock._inqueue = "" + + # end __init__ + + def __getattr__(self, key): + return getattr(self._sock, key) + # end __getattr__ + + def close(self): + self._sock.close() + self._sock = None + # end close + + def write(self, data): + self.send(data) + # end write + + def read(self, size=-1): + _sock = self._sock + _bufsize = self._bufsize + while 1: + datalen = len(_sock._inqueue) + if datalen >= size >= 0: + break + bufsize = _bufsize + if size > 0: + bufsize = min(bufsize, size - datalen ) + buf = self.recv(bufsize) + if not buf: + break + _sock._inqueue = _sock._inqueue + buf + data = _sock._inqueue + _sock._inqueue = "" + if size > 0 and datalen > size: + _sock._inqueue = data[size:] + data = data[:size] + return data + # end read + + def readline(self, size=-1): + _sock = self._sock + _bufsize = self._bufsize + while 1: + idx = string.find(_sock._inqueue, "\n") + if idx >= 0: + break + datalen = len(_sock._inqueue) + if datalen >= size >= 0: + break + bufsize = _bufsize + if size > 0: + bufsize = min(bufsize, size - datalen ) + buf = self.recv(bufsize) + if not buf: + break + _sock._inqueue = _sock._inqueue + buf + + data = _sock._inqueue + _sock._inqueue = "" + if idx >= 0: + idx = idx + 1 + _sock._inqueue = data[idx:] + data = data[:idx] + elif size > 0 and datalen > size: + _sock._inqueue = data[size:] + data = data[:size] + return data + # end readline + + def readlines(self, sizehint=-1): + result = [] + data = self.read() + while data: + idx = string.find(data, "\n") + if idx >= 0: + idx = idx + 1 + result.append( data[:idx] ) + data = data[idx:] + else: + result.append( data ) + data = "" + return result + # end readlines + + def flush(self): pass + +# end TimeoutFile + + +# +# Silently replace the socket() builtin function with +# our timeoutsocket() definition. +# +if not hasattr(socket, "_no_timeoutsocket"): + socket._no_timeoutsocket = socket.socket + socket.socket = timeoutsocket +del socket +socket = timeoutsocket +# Finis -- 2.35.1