From 67f7a388496aec53896a0a5109edf9026bbe5d5b Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 6 Jun 2002 21:08:16 +0000 Subject: SF patch 555085 (timeout socket implementation) by Michael Gilfix. I've made considerable changes to Michael's code, specifically to use the select() system call directly and to store the timeout as a C double instead of a Python object; internally, -1.0 (or anything negative) represents the None from the API. I'm not 100% sure that all corner cases are covered correctly, so please keep an eye on this. Next I'm going to try it Windows before Tim complains. No way is this a bugfix candidate. :-) --- Lib/socket.py | 133 +++++++------ Lib/test/test_socket.py | 67 ++++++- Lib/test/test_timeout.py | 136 ++++++++++++++ Modules/socketmodule.c | 474 +++++++++++++++++++++++++++++++++++++++++------ Modules/socketmodule.h | 3 + 5 files changed, 690 insertions(+), 123 deletions(-) create mode 100644 Lib/test/test_timeout.py diff --git a/Lib/socket.py b/Lib/socket.py index 153f602..04e2e33 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -134,7 +134,8 @@ def getfqdn(name=''): _socketmethods = ( 'bind', 'connect', 'connect_ex', 'fileno', 'listen', 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', - 'recv', 'recvfrom', 'send', 'sendall', 'sendto', 'setblocking', 'shutdown') + 'recv', 'recvfrom', 'send', 'sendall', 'sendto', 'setblocking', + 'settimeout', 'gettimeout', 'shutdown') class _socketobject: @@ -168,94 +169,108 @@ class _socketobject: class _fileobject: + """Implements a file object on top of a regular socket object.""" - def __init__(self, sock, mode, bufsize): + def __init__(self, sock, mode='rb', bufsize=8192): self._sock = sock self._mode = mode - if bufsize < 0: + if bufsize <= 0: bufsize = 512 - self._rbufsize = max(1, bufsize) + self._rbufsize = bufsize self._wbufsize = bufsize - self._wbuf = self._rbuf = "" + self._rbuf = [ ] + self._wbuf = [ ] def close(self): try: if self._sock: self.flush() finally: - self._sock = 0 + self._sock = None def __del__(self): self.close() def flush(self): if self._wbuf: - self._sock.sendall(self._wbuf) - self._wbuf = "" + buffer = ''.join(self._wbuf) + self._sock.sendall(buffer) + self._wbuf = [ ] - def fileno(self): + def fileno (self): return self._sock.fileno() def write(self, data): - self._wbuf = self._wbuf + data + self._wbuf.append (data) + # A _wbufsize of 1 means we're doing unbuffered IO. + # Flush accordingly. if self._wbufsize == 1: if '\n' in data: - self.flush() - else: - if len(self._wbuf) >= self._wbufsize: - self.flush() + self.flush () + elif self.__get_wbuf_len() >= self._wbufsize: + self.flush() def writelines(self, list): filter(self._sock.sendall, list) self.flush() - def read(self, n=-1): - if n >= 0: - k = len(self._rbuf) - if n <= k: - data = self._rbuf[:n] - self._rbuf = self._rbuf[n:] - return data - n = n - k - L = [self._rbuf] - self._rbuf = "" - while n > 0: - new = self._sock.recv(max(n, self._rbufsize)) - if not new: break - k = len(new) - if k > n: - L.append(new[:n]) - self._rbuf = new[n:] - break - L.append(new) - n = n - k - return "".join(L) - k = max(512, self._rbufsize) - L = [self._rbuf] - self._rbuf = "" - while 1: - new = self._sock.recv(k) - if not new: break - L.append(new) - k = min(k*2, 1024**2) - return "".join(L) - - def readline(self, limit=-1): - data = "" - i = self._rbuf.find('\n') - while i < 0 and not (0 < limit <= len(self._rbuf)): - new = self._sock.recv(self._rbufsize) - if not new: break - i = new.find('\n') - if i >= 0: i = i + len(self._rbuf) - self._rbuf = self._rbuf + new - if i < 0: i = len(self._rbuf) - else: i = i+1 - if 0 <= limit < len(self._rbuf): i = limit - data, self._rbuf = self._rbuf[:i], self._rbuf[i:] + def __get_wbuf_len (self): + buf_len = 0 + for i in [len(x) for x in self._wbuf]: + buf_len += i + return buf_len + + def __get_rbuf_len(self): + buf_len = 0 + for i in [len(x) for x in self._rbuf]: + buf_len += i + return buf_len + + def read(self, size=-1): + buf_len = self.__get_rbuf_len() + while size < 0 or buf_len < size: + recv_size = max(self._rbufsize, size - buf_len) + data = self._sock.recv(recv_size) + if not data: + break + buf_len += len(data) + self._rbuf.append(data) + data = ''.join(self._rbuf) + # Clear the rbuf at the end so we're not affected by + # an exception during a recv + self._rbuf = [ ] + if buf_len > size and size >= 0: + self._rbuf.append(data[size:]) + data = data[:size] + return data + + def readline(self, size=-1): + index = -1 + buf_len = self.__get_rbuf_len() + if len (self._rbuf): + index = min([x.find('\n') for x in self._rbuf]) + while index < 0 and (size < 0 or buf_len < size): + recv_size = max(self._rbufsize, size - buf_len) + data = self._sock.recv(recv_size) + if not data: + break + buf_len += len(data) + self._rbuf.append(data) + index = data.find('\n') + data = ''.join(self._rbuf) + self._rbuf = [ ] + index = data.find('\n') + if index >= 0: + index += 1 + elif buf_len > size: + index = size + else: + index = buf_len + self._rbuf.append(data[index:]) + data = data[:index] return data - def readlines(self, sizehint = 0): + def readlines(self, sizehint=0): total = 0 list = [] while 1: diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 9cef316..56c7fb1 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -109,6 +109,7 @@ except socket.error: canfork = hasattr(os, 'fork') try: PORT = 50007 + msg = 'socket test\n' if not canfork or os.fork(): # parent is server s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -133,13 +134,52 @@ try: f = conn.makefile() if verbose: print 'file obj:', f + data = conn.recv(1024) + if verbose: + print 'received:', data + conn.sendall(data) + + # Perform a few tests on the windows file object + if verbose: + print "Staring _fileobject tests..." + f = socket._fileobject (conn, 'rb', 8192) + first_seg = f.read(7) + second_seg = f.read(5) + if not first_seg == 'socket ' or not second_seg == 'test\n': + print "Error performing read with the python _fileobject class" + os._exit (1) + elif verbose: + print "_fileobject buffered read works" + f.write (data) + f.flush () + + buf = '' while 1: - data = conn.recv(1024) - if not data: + char = f.read(1) + if not char: + print "Error performing unbuffered read with the python ", \ + "_fileobject class" + os._exit (1) + buf += char + if buf == msg: + if verbose: + print "__fileobject unbuffered read works" break - if verbose: - print 'received:', data - conn.sendall(data) + if verbose: + # If we got this far, write() must work as well + print "__fileobject write works" + f.write(buf) + f.flush() + + line = f.readline() + if not line == msg: + print "Error perferming readline with the python _fileobject class" + os._exit (1) + f.write(line) + f.flush() + if verbose: + print "__fileobject readline works" + conn.close() else: try: @@ -149,11 +189,18 @@ try: if verbose: print 'child connecting' s.connect(("127.0.0.1", PORT)) - msg = 'socket test' - s.send(msg) - data = s.recv(1024) - if msg != data: - print 'parent/client mismatch' + + iteration = 0 + while 1: + s.send(msg) + data = s.recv(12) + if not data: + break + if msg != data: + print "parent/client mismatch. Failed in %s iteration. Received: [%s]" \ + %(iteration, data) + time.sleep (1) + iteration += 1 s.close() finally: os._exit(1) diff --git a/Lib/test/test_timeout.py b/Lib/test/test_timeout.py new file mode 100644 index 0000000..c71efa7 --- /dev/null +++ b/Lib/test/test_timeout.py @@ -0,0 +1,136 @@ +#!/home/bernie/src/python23/dist/src/python + +import unittest + +import time +import socket + +class creationTestCase(unittest.TestCase): + """Test Case for socket.gettimeout() and socket.settimeout()""" + def setUp(self): + self.__s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + def tearDown(self): + self.__s.close() + + def testObjectCreation(self): + "Test Socket creation" + self.assertEqual(self.__s.gettimeout(), None, + "Timeout socket not default to disable (None)") + + def testFloatReturnValue(self): + "Test return value of getter/setter" + self.__s.settimeout(7.345) + self.assertEqual(self.__s.gettimeout(), 7.345, + "settimeout() and gettimeout() return different result") + + self.__s.settimeout(3) + self.assertEqual(self.__s.gettimeout(), 3, + "settimeout() and gettimeout() return different result") + + def testReturnType(self): + "Test return type of getter/setter" + self.__s.settimeout(1) + self.assertEqual(type(self.__s.gettimeout()), type(1.0), + "return type of gettimeout() is not FloatType") + + self.__s.settimeout(3.9) + self.assertEqual(type(self.__s.gettimeout()), type(1.0), + "return type of gettimeout() is not FloatType") + + +class timeoutTestCase(unittest.TestCase): + """Test Case for socket.socket() timeout functions""" + def setUp(self): + self.__s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.__addr_remote = ('www.google.com', 80) + self.__addr_local = ('127.0.0.1', 25339) + + def tearDown(self): + self.__s.close() + + def testConnectTimeout(self): + "Test connect() timeout" + _timeout = 0.02 + self.__s.settimeout(_timeout) + + _t1 = time.time() + self.failUnlessRaises(socket.error, self.__s.connect, + self.__addr_remote) + _t2 = time.time() + + _delta = abs(_t1 - _t2) + self.assert_(_delta < _timeout + 0.5, + "timeout (%f) is 0.5 seconds more than required (%f)" + %(_delta, _timeout)) + + def testRecvTimeout(self): + "Test recv() timeout" + _timeout = 0.02 + self.__s.connect(self.__addr_remote) + self.__s.settimeout(_timeout) + + _t1 = time.time() + self.failUnlessRaises(socket.error, self.__s.recv, 1024) + _t2 = time.time() + + _delta = abs(_t1 - _t2) + self.assert_(_delta < _timeout + 0.5, + "timeout (%f) is 0.5 seconds more than required (%f)" + %(_delta, _timeout)) + + def testAcceptTimeout(self): + "Test accept() timeout()" + _timeout = 2 + self.__s.settimeout(_timeout) + self.__s.bind(self.__addr_local) + self.__s.listen(5) + + _t1 = time.time() + self.failUnlessRaises(socket.error, self.__s.accept) + _t2 = time.time() + + _delta = abs(_t1 - _t2) + self.assert_(_delta < _timeout + 0.5, + "timeout (%f) is 0.5 seconds more than required (%f)" + %(_delta, _timeout)) + + def testRecvfromTimeout(self): + "Test recvfrom() timeout()" + _timeout = 2 + self.__s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.__s.settimeout(_timeout) + self.__s.bind(self.__addr_local) + + _t1 = time.time() + self.failUnlessRaises(socket.error, self.__s.recvfrom, 8192) + _t2 = time.time() + + _delta = abs(_t1 - _t2) + self.assert_(_delta < _timeout + 0.5, + "timeout (%f) is 0.5 seconds more than required (%f)" + %(_delta, _timeout)) + + def testSend(self): + "Test send() timeout" + # couldn't figure out how to test it + pass + + def testSendto(self): + "Test sendto() timeout" + # couldn't figure out how to test it + pass + + def testSendall(self): + "Test sendall() timeout" + # couldn't figure out how to test it + pass + + +def suite(): + suite = unittest.TestSuite() + + return suite + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 89e8da6..06d3f79 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -66,6 +66,8 @@ Socket methods: - s.sendall(string [,flags]) # tries to send everything in a loop - s.sendto(string, [flags,] sockaddr) --> nbytes - s.setblocking(0 | 1) --> None +- s.settimeout(None | float) -> None # Argument in seconds +- s.gettimeout() -> None or float seconds - s.setsockopt(level, optname, value) --> None - s.shutdown(how) --> None - repr(s) --> "" @@ -421,6 +423,107 @@ PyGAI_Err(int error) return NULL; } +/* For timeout errors */ +static PyObject * +timeout_err(void) +{ + PyObject *v; + +#ifdef MS_WINDOWS + v = Py_BuildValue("(is)", WSAETIMEDOUT, "Socket operation timed out"); +#else + v = Py_BuildValue("(is)", ETIMEDOUT, "Socket operation timed out"); +#endif + + if (v != NULL) { + PyErr_SetObject(PySocket_Error, v); + Py_DECREF(v); + } + + return NULL; +} + +/* Function to perfrom the setting of socket blocking mode + * internally. block = (1 | 0). + */ +static int +internal_setblocking(PySocketSockObject *s, int block) +{ +#ifndef RISCOS +#ifndef MS_WINDOWS + int delay_flag; +#endif +#endif + + Py_BEGIN_ALLOW_THREADS +#ifdef __BEOS__ + block = !block; + setsockopt( s->sock_fd, SOL_SOCKET, SO_NONBLOCK, + (void *)(&block), sizeof(int)); +#else +#ifndef RISCOS +#ifndef MS_WINDOWS +#if defined(PYOS_OS2) && !defined(PYCC_GCC) + block = !block; + ioctl(s->sock_fd, FIONBIO, (caddr_t)&block, sizeof(block)); +#else /* !PYOS_OS2 */ + delay_flag = fcntl(s->sock_fd, F_GETFL, 0); + if (block) + delay_flag &= (~O_NDELAY); + else + delay_flag |= O_NDELAY; + fcntl(s->sock_fd, F_SETFL, delay_flag); +#endif /* !PYOS_OS2 */ +#else /* MS_WINDOWS */ + block = !block; + ioctlsocket(s->sock_fd, FIONBIO, (u_long*)&block); +#endif /* MS_WINDOWS */ +#endif /* __BEOS__ */ +#endif /* RISCOS */ + Py_END_ALLOW_THREADS + + /* Since these don't return anything */ + return 1; +} + +/* For access to the select module to poll the socket for timeout + * functionality. If reading is: 1 poll as read, 0, poll as write. + * Return value: -1 if error, 0 if not ready, >= 1 if ready. + */ +static int +internal_select(PySocketSockObject *s, int reading) +{ + fd_set fds; + struct timeval tv; + int count; + + /* Construct the arguments to select */ + tv.tv_sec = (int)s->sock_timeout; + tv.tv_usec = (int)(s->sock_timeout/1e6); + FD_ZERO(&fds); + FD_SET(s->sock_fd, &fds); + + /* See if the socket is ready */ + if (reading) + count = select(s->sock_fd, &fds, NULL, NULL, &tv); + else + count = select(s->sock_fd, NULL, &fds, NULL, &tv); + + /* Check for errors */ + if (count < 0) { + s->errorhandler(); + return -1; + } + + /* Set the error if the timeout has elapsed, i.e, we were not + * polled. + */ + if (count == 0) + timeout_err(); + + return count; +} + /* Initialize a new socket object. */ static void @@ -434,6 +537,9 @@ init_sockobject(PySocketSockObject *s, s->sock_family = family; s->sock_type = type; s->sock_proto = proto; + s->sock_blocking = 1; /* Start in blocking mode */ + s->sock_timeout = -1.0; /* Start without timeout */ + s->errorhandler = &PySocket_Err; #ifdef RISCOS if(taskwindow) { @@ -691,7 +797,7 @@ getsockaddrarg(PySocketSockObject *s, PyObject *args, struct sockaddr_un* addr; char *path; int len; - addr = (struct sockaddr_un* )&(s->sock_addr).un; + addr = (struct sockaddr_un*)&(s->sock_addr).un; if (!PyArg_Parse(args, "t#", &path, &len)) return 0; if (len > sizeof addr->sun_path) { @@ -861,9 +967,47 @@ PySocketSock_accept(PySocketSockObject *s) if (!getsockaddrlen(s, &addrlen)) return NULL; memset(addrbuf, 0, addrlen); + + errno = 0; /* Reset indicator for use with timeout behavior */ + Py_BEGIN_ALLOW_THREADS newfd = accept(s->sock_fd, (struct sockaddr *) addrbuf, &addrlen); Py_END_ALLOW_THREADS + + if (s->sock_timeout >= 0.0) { +#ifdef MS_WINDOWS + if (newfd == INVALID_SOCKET) + if (!s->sock_blocking) + return s->errorhandler(); + /* Check if we have a true failure + for a blocking socket */ + if (errno != WSAEWOULDBLOCK) + return s->errorhandler(); +#else + if (newfd < 0) { + if (!s->sock_blocking) + return s->errorhandler(); + /* Check if we have a true failure + for a blocking socket */ + if (errno != EAGAIN && errno != EWOULDBLOCK) + return s->errorhandler(); + } +#endif + + /* try waiting the timeout period */ + if (internal_select(s, 0) <= 0) + return NULL; + + Py_BEGIN_ALLOW_THREADS + newfd = accept(s->sock_fd, + (struct sockaddr *)addrbuf, + &addrlen); + Py_END_ALLOW_THREADS + } + + /* At this point, we really have an error, whether using timeout + * behavior or regular socket behavior + */ #ifdef MS_WINDOWS if (newfd == INVALID_SOCKET) #else @@ -877,18 +1021,19 @@ PySocketSock_accept(PySocketSockObject *s) s->sock_family, s->sock_type, s->sock_proto); + if (sock == NULL) { SOCKETCLOSE(newfd); goto finally; } addr = makesockaddr(s->sock_fd, (struct sockaddr *)addrbuf, - addrlen); + addrlen); if (addr == NULL) goto finally; res = Py_BuildValue("OO", sock, addr); - finally: +finally: Py_XDECREF(sock); Py_XDECREF(addr); return res; @@ -901,47 +1046,24 @@ Wait for an incoming connection. Return a new socket representing the\n\ connection, and the address of the client. For IP sockets, the address\n\ info is a pair (hostaddr, port)."; - /* s.setblocking(1 | 0) method */ static PyObject * PySocketSock_setblocking(PySocketSockObject *s, PyObject *arg) { int block; -#ifndef RISCOS -#ifndef MS_WINDOWS - int delay_flag; -#endif -#endif + block = PyInt_AsLong(arg); if (block == -1 && PyErr_Occurred()) return NULL; - Py_BEGIN_ALLOW_THREADS -#ifdef __BEOS__ - block = !block; - setsockopt( s->sock_fd, SOL_SOCKET, SO_NONBLOCK, - (void *)(&block), sizeof( int ) ); -#else -#ifndef RISCOS -#ifndef MS_WINDOWS -#if defined(PYOS_OS2) && !defined(PYCC_GCC) - block = !block; - ioctl(s->sock_fd, FIONBIO, (caddr_t)&block, sizeof(block)); -#else /* !PYOS_OS2 */ - delay_flag = fcntl (s->sock_fd, F_GETFL, 0); - if (block) - delay_flag &= (~O_NDELAY); - else - delay_flag |= O_NDELAY; - fcntl (s->sock_fd, F_SETFL, delay_flag); -#endif /* !PYOS_OS2 */ -#else /* MS_WINDOWS */ - block = !block; - ioctlsocket(s->sock_fd, FIONBIO, (u_long*)&block); -#endif /* MS_WINDOWS */ -#endif /* __BEOS__ */ -#endif /* RISCOS */ - Py_END_ALLOW_THREADS + + s->sock_blocking = block; + + /* If we're not using timeouts, actually set the blocking to give + * old python behavior. + */ + if (s->sock_timeout < 0.0) + internal_setblocking(s, block); Py_INCREF(Py_None); return Py_None; @@ -953,6 +1075,81 @@ static char setblocking_doc[] = Set the socket to blocking (flag is true) or non-blocking (false).\n\ This uses the FIONBIO ioctl with the O_NDELAY flag."; +/* s.settimeout (float | int | long) method. + * Causes an exception to be raised when the integer number of seconds + * has elapsed when performing a blocking socket operation. + */ +static PyObject * +PySocketSock_settimeout(PySocketSockObject *s, PyObject *arg) +{ + double value; + + if (arg == Py_None) + value = -1.0; + else { + value = PyFloat_AsDouble(arg); + if (value < 0.0) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_ValueError, + "Invalid timeout value"); + return NULL; + } + } + + s->sock_timeout = value; + + /* The semantics of setting socket timeouts are: + * If you settimeout(!=None): + * The actual socket gets put in non-blocking mode and the select + * is used to control timeouts. + * Else if you settimeout(None) [then value is -1.0]: + * The old behavior is used AND automatically, the socket is set + * to blocking mode. That means that someone who was doing + * non-blocking stuff before, sets a timeout, and then unsets + * one, will have to call setblocking(0) again if he wants + * non-blocking stuff. This makes sense because timeout stuff is + * blocking by nature. + */ + if (value < 0.0) + internal_setblocking(s, 0); + else + internal_setblocking(s, 1); + + s->sock_blocking = 1; /* Always negate setblocking() */ + + Py_INCREF(Py_None); + return Py_None; +} + +static char settimeout_doc[] = +"settimeout(seconds)\n\ +\n\ +Set a timeout on blocking socket operations. 'seconds' can be a floating,\n\ +integer, or long number of seconds, or the None value. Socket operations\n\ +will raise an exception if the timeout period has elapsed before the\n\ +operation has completed. Setting a timeout of None disables timeouts\n\ +on socket operations."; + +/* s.gettimeout () method. + * Returns the timeout associated with a socket. + */ +static PyObject * +PySocketSock_gettimeout(PySocketSockObject *s) +{ + if (s->sock_timeout < 0.0) { + Py_INCREF(Py_None); + return Py_None; + } + else + return PyFloat_FromDouble(s->sock_timeout); +} + +static char gettimeout_doc[] = +"gettimeout()\n\ +\n\ +Returns the timeout in floating seconds associated with socket \n\ +operations. A timeout of None indicates that timeouts on socket \n\ +operations are disabled."; #ifdef RISCOS /* s.sleeptaskw(1 | 0) method */ @@ -960,16 +1157,16 @@ This uses the FIONBIO ioctl with the O_NDELAY flag."; static PyObject * PySocketSock_sleeptaskw(PySocketSockObject *s,PyObject *args) { - int block; - int delay_flag; - if (!PyArg_Parse(args, "i", &block)) - return NULL; - Py_BEGIN_ALLOW_THREADS - socketioctl(s->sock_fd, 0x80046679, (u_long*)&block); - Py_END_ALLOW_THREADS + int block; + int delay_flag; + if (!PyArg_Parse(args, "i", &block)) + return NULL; + Py_BEGIN_ALLOW_THREADS + socketioctl(s->sock_fd, 0x80046679, (u_long*)&block); + Py_END_ALLOW_THREADS - Py_INCREF(Py_None); - return Py_None; + Py_INCREF(Py_None); + return Py_None; } static char sleeptaskw_doc[] = "sleeptaskw(flag)\n\ @@ -1142,11 +1339,50 @@ PySocketSock_connect(PySocketSockObject *s, PyObject *addro) if (!getsockaddrarg(s, addro, &addr, &addrlen)) return NULL; + + errno = 0; /* Reset the err indicator for use with timeouts */ + Py_BEGIN_ALLOW_THREADS res = connect(s->sock_fd, addr, addrlen); Py_END_ALLOW_THREADS + + if (s->sock_timeout >= 0.0) { + if (res < 0) { + /* Return if we're already connected */ +#ifdef MS_WINDOWS + if (errno == WSAEINVAL || errno == WSAEISCONN) +#else + if (errno == EISCONN) +#endif + goto connected; + + /* Check if we have an error */ + if (!s->sock_blocking) + return s->errorhandler (); + /* Check if we have a true failure for a blocking socket */ +#ifdef MS_WINDOWS + if (errno != WSAEWOULDBLOCK) +#else + if (errno != EINPROGRESS && errno != EALREADY && + errno != EWOULDBLOCK) +#endif + return s->errorhandler(); + } + + /* Check if we're ready for the connect via select */ + if (internal_select(s, 1) <= 0) + return NULL; + + /* Complete the connection now */ + Py_BEGIN_ALLOW_THREADS + res = connect(s->sock_fd, addr, addrlen); + Py_END_ALLOW_THREADS + } + if (res < 0) return s->errorhandler(); + +connected: Py_INCREF(Py_None); return Py_None; } @@ -1169,9 +1405,46 @@ PySocketSock_connect_ex(PySocketSockObject *s, PyObject *addro) if (!getsockaddrarg(s, addro, &addr, &addrlen)) return NULL; + + errno = 0; /* Reset the err indicator for use with timeouts */ + Py_BEGIN_ALLOW_THREADS res = connect(s->sock_fd, addr, addrlen); Py_END_ALLOW_THREADS + + if (s->sock_timeout >= 0.0) { + if (res < 0) { + /* Return if we're already connected */ +#ifdef MS_WINDOWS + if (errno == WSAEINVAL || errno == WSAEISCONN) +#else + if (errno == EISCONN) +#endif + goto conex_finally; + + /* Check if we have an error */ + if (!s->sock_blocking) + goto conex_finally; + /* Check if we have a true failure for a blocking socket */ +#ifdef MS_WINDOWS + if (errno != WSAEWOULDBLOCK) +#else + if (errno != EINPROGRESS && errno != EALREADY && + errno != EWOULDBLOCK) +#endif + goto conex_finally; + } + + /* Check if we're ready for the connect via select */ + if (internal_select(s, 1) <= 0) + return NULL; + + /* Complete the connection now */ + Py_BEGIN_ALLOW_THREADS + res = connect(s->sock_fd, addr, addrlen); + Py_END_ALLOW_THREADS + } + if (res != 0) { #ifdef MS_WINDOWS res = WSAGetLastError(); @@ -1179,6 +1452,8 @@ PySocketSock_connect_ex(PySocketSockObject *s, PyObject *addro) res = errno; #endif } + +conex_finally: return PyInt_FromLong((long) res); } @@ -1360,7 +1635,7 @@ PySocketSock_makefile(PySocketSockObject *s, PyObject *args) } #ifdef USE_GUSI2 /* Workaround for bug in Metrowerks MSL vs. GUSI I/O library */ - if (strchr(mode, 'b') != NULL ) + if (strchr(mode, 'b') != NULL) bufsize = 0; #endif f = PyFile_FromFile(fp, "", mode, fclose); @@ -1385,19 +1660,31 @@ PySocketSock_recv(PySocketSockObject *s, PyObject *args) { int len, n, flags = 0; PyObject *buf; + if (!PyArg_ParseTuple(args, "i|i:recv", &len, &flags)) return NULL; - if (len < 0) { + + if (len < 0) { PyErr_SetString(PyExc_ValueError, "negative buffersize in connect"); return NULL; } + buf = PyString_FromStringAndSize((char *) 0, len); if (buf == NULL) return NULL; + + if (s->sock_timeout >= 0.0) { + if (s->sock_blocking) { + if (internal_select(s, 0) <= 0) + return NULL; + } + } + Py_BEGIN_ALLOW_THREADS n = recv(s->sock_fd, PyString_AS_STRING(buf), len, flags); Py_END_ALLOW_THREADS + if (n < 0) { Py_DECREF(buf); return s->errorhandler(); @@ -1425,16 +1712,25 @@ PySocketSock_recvfrom(PySocketSockObject *s, PyObject *args) PyObject *buf = NULL; PyObject *addr = NULL; PyObject *ret = NULL; - int len, n, flags = 0; socklen_t addrlen; + if (!PyArg_ParseTuple(args, "i|i:recvfrom", &len, &flags)) return NULL; + if (!getsockaddrlen(s, &addrlen)) return NULL; buf = PyString_FromStringAndSize((char *) 0, len); if (buf == NULL) return NULL; + + if (s->sock_timeout >= 0.0) { + if (s->sock_blocking) { + if (internal_select(s, 0) <= 0) + return NULL; + } + } + Py_BEGIN_ALLOW_THREADS memset(addrbuf, 0, addrlen); n = recvfrom(s->sock_fd, PyString_AS_STRING(buf), len, flags, @@ -1449,18 +1745,22 @@ PySocketSock_recvfrom(PySocketSockObject *s, PyObject *args) #endif ); Py_END_ALLOW_THREADS + if (n < 0) { Py_DECREF(buf); return s->errorhandler(); } + if (n != len && _PyString_Resize(&buf, n) < 0) return NULL; - if (!(addr = makesockaddr(s->sock_fd, (struct sockaddr *)addrbuf, addrlen))) + if (!(addr = makesockaddr(s->sock_fd, (struct sockaddr *)addrbuf, + addrlen))) goto finally; ret = Py_BuildValue("OO", buf, addr); - finally: + +finally: Py_XDECREF(addr); Py_XDECREF(buf); return ret; @@ -1471,7 +1771,6 @@ static char recvfrom_doc[] = \n\ Like recv(buffersize, flags) but also return the sender's address info."; - /* s.send(data [,flags]) method */ static PyObject * @@ -1479,11 +1778,21 @@ PySocketSock_send(PySocketSockObject *s, PyObject *args) { char *buf; int len, n, flags = 0; + if (!PyArg_ParseTuple(args, "s#|i:send", &buf, &len, &flags)) return NULL; + + if (s->sock_timeout >= 0.0) { + if (s->sock_blocking) { + if (internal_select(s, 1) <= 0) + return NULL; + } + } + Py_BEGIN_ALLOW_THREADS n = send(s->sock_fd, buf, len, flags); Py_END_ALLOW_THREADS + if (n < 0) return s->errorhandler(); return PyInt_FromLong((long)n); @@ -1504,8 +1813,17 @@ PySocketSock_sendall(PySocketSockObject *s, PyObject *args) { char *buf; int len, n, flags = 0; + if (!PyArg_ParseTuple(args, "s#|i:sendall", &buf, &len, &flags)) return NULL; + + if (s->sock_timeout >= 0.0) { + if (s->sock_blocking) { + if (internal_select(s, 1) < 0) + return NULL; + } + } + Py_BEGIN_ALLOW_THREADS do { n = send(s->sock_fd, buf, len, flags); @@ -1515,8 +1833,10 @@ PySocketSock_sendall(PySocketSockObject *s, PyObject *args) len -= n; } while (len > 0); Py_END_ALLOW_THREADS + if (n < 0) return s->errorhandler(); + Py_INCREF(Py_None); return Py_None; } @@ -1539,6 +1859,7 @@ PySocketSock_sendto(PySocketSockObject *s, PyObject *args) char *buf; struct sockaddr *addr; int addrlen, len, n, flags; + flags = 0; if (!PyArg_ParseTuple(args, "s#O:sendto", &buf, &len, &addro)) { PyErr_Clear(); @@ -1546,11 +1867,21 @@ PySocketSock_sendto(PySocketSockObject *s, PyObject *args) &buf, &len, &flags, &addro)) return NULL; } + if (!getsockaddrarg(s, addro, &addr, &addrlen)) return NULL; + + if (s->sock_timeout >= 0.0) { + if (s->sock_blocking) { + if (internal_select(s, 1) <= 0) + return NULL; + } + } + Py_BEGIN_ALLOW_THREADS n = sendto(s->sock_fd, buf, len, flags, addr, addrlen); Py_END_ALLOW_THREADS + if (n < 0) return s->errorhandler(); return PyInt_FromLong((long)n); @@ -1635,6 +1966,10 @@ static PyMethodDef PySocketSock_methods[] = { sendto_doc}, {"setblocking", (PyCFunction)PySocketSock_setblocking, METH_O, setblocking_doc}, + {"settimeout", (PyCFunction)PySocketSock_settimeout, METH_O, + settimeout_doc}, + {"gettimeout", (PyCFunction)PySocketSock_gettimeout, METH_NOARGS, + gettimeout_doc}, {"setsockopt", (PyCFunction)PySocketSock_setsockopt, METH_VARARGS, setsockopt_doc}, {"shutdown", (PyCFunction)PySocketSock_shutdown, METH_O, @@ -1692,6 +2027,7 @@ PySocketSock_new(PyTypeObject *type, PyObject *args, PyObject *kwds) new = type->tp_alloc(type, 0); if (new != NULL) { ((PySocketSockObject *)new)->sock_fd = -1; + ((PySocketSockObject *)new)->sock_timeout = -1.0; ((PySocketSockObject *)new)->errorhandler = &PySocket_Err; } return new; @@ -1713,9 +2049,11 @@ PySocketSock_init(PyObject *self, PyObject *args, PyObject *kwds) "|iii:socket", keywords, &family, &type, &proto)) return -1; + Py_BEGIN_ALLOW_THREADS fd = socket(family, type, proto); Py_END_ALLOW_THREADS + #ifdef MS_WINDOWS if (fd == INVALID_SOCKET) #else @@ -1731,7 +2069,9 @@ PySocketSock_init(PyObject *self, PyObject *args, PyObject *kwds) #ifdef SIGPIPE (void) signal(SIGPIPE, SIG_IGN); #endif + return 0; + } @@ -1885,6 +2225,7 @@ gethost_common(struct hostent *h, struct sockaddr *addr, int alen, int af) #endif return NULL; } + if (h->h_addrtype != af) { #ifdef HAVE_STRERROR /* Let's get real error message to return */ @@ -1895,35 +2236,47 @@ gethost_common(struct hostent *h, struct sockaddr *addr, int alen, int af) #endif return NULL; } + switch (af) { + case AF_INET: if (alen < sizeof(struct sockaddr_in)) return NULL; break; + #ifdef ENABLE_IPV6 case AF_INET6: if (alen < sizeof(struct sockaddr_in6)) return NULL; break; #endif + } + if ((name_list = PyList_New(0)) == NULL) goto err; + if ((addr_list = PyList_New(0)) == NULL) goto err; + for (pch = h->h_aliases; *pch != NULL; pch++) { int status; tmp = PyString_FromString(*pch); if (tmp == NULL) goto err; + status = PyList_Append(name_list, tmp); Py_DECREF(tmp); + if (status) goto err; } + for (pch = h->h_addr_list; *pch != NULL; pch++) { int status; + switch (af) { + case AF_INET: { struct sockaddr_in sin; @@ -1934,10 +2287,12 @@ gethost_common(struct hostent *h, struct sockaddr *addr, int alen, int af) #endif memcpy(&sin.sin_addr, *pch, sizeof(sin.sin_addr)); tmp = makeipaddr((struct sockaddr *)&sin, sizeof(sin)); + if (pch == h->h_addr_list && alen >= sizeof(sin)) memcpy((char *) addr, &sin, sizeof(sin)); break; } + #ifdef ENABLE_IPV6 case AF_INET6: { @@ -1950,24 +2305,31 @@ gethost_common(struct hostent *h, struct sockaddr *addr, int alen, int af) memcpy(&sin6.sin6_addr, *pch, sizeof(sin6.sin6_addr)); tmp = makeipaddr((struct sockaddr *)&sin6, sizeof(sin6)); + if (pch == h->h_addr_list && alen >= sizeof(sin6)) memcpy((char *) addr, &sin6, sizeof(sin6)); break; } #endif + default: /* can't happen */ PyErr_SetString(PySocket_Error, "unsupported address family"); return NULL; } + if (tmp == NULL) goto err; + status = PyList_Append(addr_list, tmp); Py_DECREF(tmp); + if (status) goto err; } + rtn_tuple = Py_BuildValue("sOO", h->h_name, name_list, addr_list); + err: Py_XDECREF(name_list); Py_XDECREF(addr_list); @@ -2022,10 +2384,14 @@ PySocket_gethostbyname_ex(PyObject *self, PyObject *args) h = gethostbyname(name); #endif /* HAVE_GETHOSTBYNAME_R */ Py_END_ALLOW_THREADS - /* Some C libraries would require addr.__ss_family instead of addr.ss_family. - Therefore, we cast the sockaddr_storage into sockaddr to access sa_family. */ + /* Some C libraries would require addr.__ss_family instead of + * addr.ss_family. + * Therefore, we cast the sockaddr_storage into sockaddr to + * access sa_family. + */ sa = (struct sockaddr*)&addr; - ret = gethost_common(h, (struct sockaddr *)&addr, sizeof(addr), sa->sa_family); + ret = gethost_common(h, (struct sockaddr *)&addr, sizeof(addr), + sa->sa_family); #ifdef USE_GETHOSTBYNAME_LOCK PyThread_release_lock(gethostbyname_lock); #endif @@ -2170,7 +2536,7 @@ PySocket_getprotobyname(PyObject *self, PyObject *args) struct protoent *sp; #ifdef __BEOS__ /* Not available in BeOS yet. - [cjh] */ - PyErr_SetString( PySocket_Error, "getprotobyname not supported" ); + PyErr_SetString(PySocket_Error, "getprotobyname not supported"); return NULL; #else if (!PyArg_ParseTuple(args, "s:getprotobyname", &name)) diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h index a59e6f7..4d17f08 100644 --- a/Modules/socketmodule.h +++ b/Modules/socketmodule.h @@ -83,6 +83,9 @@ typedef struct { PyObject *(*errorhandler)(void); /* Error handler; checks errno, returns NULL and sets a Python exception */ + int sock_blocking; /* Flag indicated whether the + socket is in blocking mode */ + double sock_timeout; /* Operation timeout value */ } PySocketSockObject; /* --- C API ----------------------------------------------------*/ -- cgit v0.12