summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2002-06-06 21:08:16 (GMT)
committerGuido van Rossum <guido@python.org>2002-06-06 21:08:16 (GMT)
commit67f7a388496aec53896a0a5109edf9026bbe5d5b (patch)
treeafa4d86d404b77167e7da09152db2a3562b729a2
parentc9a55776c88619d1b79d47d4734af0f2c423c045 (diff)
downloadcpython-67f7a388496aec53896a0a5109edf9026bbe5d5b.zip
cpython-67f7a388496aec53896a0a5109edf9026bbe5d5b.tar.gz
cpython-67f7a388496aec53896a0a5109edf9026bbe5d5b.tar.bz2
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. :-)
-rw-r--r--Lib/socket.py133
-rw-r--r--Lib/test/test_socket.py67
-rw-r--r--Lib/test/test_timeout.py136
-rw-r--r--Modules/socketmodule.c474
-rw-r--r--Modules/socketmodule.h3
5 files changed, 690 insertions, 123 deletions
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) --> "<socket object, fd=%d, family=%d, type=%d, protocol=%d>"
@@ -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, "<socket>", 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 ----------------------------------------------------*/