summaryrefslogtreecommitdiffstats
path: root/Modules
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>2002-06-13 15:07:44 (GMT)
committerGuido van Rossum <guido@python.org>2002-06-13 15:07:44 (GMT)
commit11ba094957ed72d0ecc9e50e79d138a0770df6e3 (patch)
tree029b200e2794fdb9231f42cc77d3120b81ac5c96 /Modules
parentdfad1a9039df367c9a403e2b777fe2690f3b5b88 (diff)
downloadcpython-11ba094957ed72d0ecc9e50e79d138a0770df6e3.zip
cpython-11ba094957ed72d0ecc9e50e79d138a0770df6e3.tar.gz
cpython-11ba094957ed72d0ecc9e50e79d138a0770df6e3.tar.bz2
Major overhaul of timeout sockets:
- setblocking(0) and settimeout(0) are now equivalent, and ditto for setblocking(1) and settimeout(None). - Don't raise an exception from internal_select(); let the final call report the error (this means you will get an EAGAIN error instead of an ETIMEDOUT error -- I don't care). - Move the select to inside the Py_{BEGIN,END}_ALLOW_THREADS brackets, so other theads can run (this was a bug in the original code). - Redid the retry logic in connect() and connect_ex() to avoid masking errors. This probably doesn't work for Windows yet; I'll fix that next. It may also fail on other platforms, depending on what retrying a connect does; I need help with this. - Get rid of the retry logic in accept(). I don't think it was needed at all. But I may be wrong.
Diffstat (limited to 'Modules')
-rw-r--r--Modules/socketmodule.c277
-rw-r--r--Modules/socketmodule.h5
2 files changed, 60 insertions, 222 deletions
diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c
index ff67c3d..3896831 100644
--- a/Modules/socketmodule.c
+++ b/Modules/socketmodule.c
@@ -446,26 +446,6 @@ set_gaierror(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(socket_error, v);
- Py_DECREF(v);
- }
-
- return NULL;
-}
-
/* Function to perform the setting of socket blocking mode
internally. block = (1 | 0). */
static int
@@ -508,16 +488,18 @@ internal_setblocking(PySocketSockObject *s, int block)
return 1;
}
-/* For access to the select module to poll the socket for timeout
- functionality. writing is 1 for writing, 0 for reading.
- Return value: -1 if error, 0 if not ready, >= 1 if ready.
- An exception is set when the return value is <= 0 (!). */
-static int
+/* Do a select() on the socket, if necessary (sock_timeout > 0).
+ The argument writing indicates the direction.
+ This does not raise an exception or return a success indicator;
+ we'll let the actual socket call do that. */
+static void
internal_select(PySocketSockObject *s, int writing)
{
fd_set fds;
struct timeval tv;
- int count;
+
+ if (s->sock_timeout <= 0.0)
+ return;
/* Construct the arguments to select */
tv.tv_sec = (int)s->sock_timeout;
@@ -527,22 +509,9 @@ internal_select(PySocketSockObject *s, int writing)
/* See if the socket is ready */
if (writing)
- count = select(s->sock_fd+1, NULL, &fds, NULL, &tv);
+ select(s->sock_fd+1, NULL, &fds, NULL, &tv);
else
- count = select(s->sock_fd+1, &fds, NULL, 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;
+ select(s->sock_fd+1, &fds, NULL, NULL, &tv);
}
/* Initialize a new socket object. */
@@ -558,7 +527,6 @@ 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 = &set_error;
@@ -997,45 +965,11 @@ sock_accept(PySocketSockObject *s)
return NULL;
memset(addrbuf, 0, addrlen);
- errno = 0; /* Reset indicator for use with timeout behavior */
-
Py_BEGIN_ALLOW_THREADS
+ internal_select(s, 0);
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
@@ -1074,7 +1008,10 @@ 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 */
+/* s.setblocking(flag) method. Argument:
+ False -- non-blocking mode; same as settimeout(0)
+ True -- blocking mode; same as settimeout(None)
+*/
static PyObject *
sock_setblocking(PySocketSockObject *s, PyObject *arg)
@@ -1085,8 +1022,7 @@ sock_setblocking(PySocketSockObject *s, PyObject *arg)
if (block == -1 && PyErr_Occurred())
return NULL;
- s->sock_blocking = block;
- s->sock_timeout = -1.0; /* Always clear the timeout */
+ s->sock_timeout = block ? -1.0 : 0.0;
internal_setblocking(s, block);
Py_INCREF(Py_None);
@@ -1097,44 +1033,34 @@ static char setblocking_doc[] =
"setblocking(flag)\n\
\n\
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(None | float) method.
- Causes an exception to be raised when the given time has
- elapsed when performing a blocking socket operation. */
+setblocking(True) is equivalent to settimeout(None);\n\
+setblocking(False) is equivalent to settimeout(0.0).";
+
+/* s.settimeout(timeout) method. Argument:
+ None -- no timeout, blocking mode; same as setblocking(True)
+ 0.0 -- non-blocking mode; same as setblocking(False)
+ > 0 -- timeout mode; operations time out after timeout seconds
+ < 0 -- illegal; raises an exception
+*/
static PyObject *
sock_settimeout(PySocketSockObject *s, PyObject *arg)
{
- double value;
+ double timeout;
if (arg == Py_None)
- value = -1.0;
+ timeout = -1.0;
else {
- value = PyFloat_AsDouble(arg);
- if (value < 0.0) {
+ timeout = PyFloat_AsDouble(arg);
+ if (timeout < 0.0) {
if (!PyErr_Occurred())
PyErr_SetString(PyExc_ValueError,
- "Invalid timeout value");
+ "Timeout value out of range");
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. */
- internal_setblocking(s, value < 0.0);
-
- s->sock_blocking = 1; /* Always negate setblocking() */
+ s->sock_timeout = timeout;
+ internal_setblocking(s, timeout < 0.0);
Py_INCREF(Py_None);
return Py_None;
@@ -1143,8 +1069,10 @@ sock_settimeout(PySocketSockObject *s, PyObject *arg)
static char settimeout_doc[] =
"settimeout(timeout)\n\
\n\
-Set a timeout on blocking socket operations. 'timeout' can be a float,\n\
-giving seconds, or None. Setting a timeout of None disables timeout.";
+Set a timeout on socket operations. 'timeout' can be a float,\n\
+giving in seconds, or None. Setting a timeout of None disables\n\
+the timeout feature and is equivalent to setblocking(1).\n\
+Setting a timeout of zero is the same as setblocking(0).";
/* s.gettimeout() method.
Returns the timeout associated with a socket. */
@@ -1355,50 +1283,20 @@ sock_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
+ if (s->sock_timeout > 0.0) {
res = connect(s->sock_fd, addr, addrlen);
- Py_END_ALLOW_THREADS
+ if (res == EINPROGRESS) {
+ internal_select(s, 1);
+ res = connect(s->sock_fd, addr, addrlen);
+ }
}
+ else
+ 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;
}
@@ -1422,47 +1320,18 @@ sock_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
+ if (s->sock_timeout > 0.0) {
res = connect(s->sock_fd, addr, addrlen);
- Py_END_ALLOW_THREADS
+ if (res == EINPROGRESS) {
+ internal_select(s, 1);
+ res = connect(s->sock_fd, addr, addrlen);
+ }
}
+ else
+ res = connect(s->sock_fd, addr, addrlen);
+ Py_END_ALLOW_THREADS
-conex_finally:
if (res != 0) {
#ifdef MS_WINDOWS
res = WSAGetLastError();
@@ -1683,7 +1552,7 @@ sock_recv(PySocketSockObject *s, PyObject *args)
if (len < 0) {
PyErr_SetString(PyExc_ValueError,
- "negative buffersize in connect");
+ "negative buffersize in recv");
return NULL;
}
@@ -1691,14 +1560,8 @@ sock_recv(PySocketSockObject *s, PyObject *args)
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
+ internal_select(s, 0);
n = recv(s->sock_fd, PyString_AS_STRING(buf), len, flags);
Py_END_ALLOW_THREADS
@@ -1741,15 +1604,9 @@ sock_recvfrom(PySocketSockObject *s, PyObject *args)
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);
+ internal_select(s, 0);
n = recvfrom(s->sock_fd, PyString_AS_STRING(buf), len, flags,
#ifndef MS_WINDOWS
#if defined(PYOS_OS2) && !defined(PYCC_GCC)
@@ -1799,14 +1656,8 @@ sock_send(PySocketSockObject *s, PyObject *args)
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
+ internal_select(s, 1);
n = send(s->sock_fd, buf, len, flags);
Py_END_ALLOW_THREADS
@@ -1834,14 +1685,8 @@ sock_sendall(PySocketSockObject *s, PyObject *args)
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
+ internal_select(s, 1);
do {
n = send(s->sock_fd, buf, len, flags);
if (n < 0)
@@ -1888,14 +1733,8 @@ sock_sendto(PySocketSockObject *s, PyObject *args)
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
+ internal_select(s, 1);
n = sendto(s->sock_fd, buf, len, flags, addr, addrlen);
Py_END_ALLOW_THREADS
diff --git a/Modules/socketmodule.h b/Modules/socketmodule.h
index 0c6cd1b..936acac 100644
--- a/Modules/socketmodule.h
+++ b/Modules/socketmodule.h
@@ -83,9 +83,8 @@ 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 */
+ double sock_timeout; /* Operation timeout in seconds;
+ 0.0 means non-blocking */
} PySocketSockObject;
/* --- C API ----------------------------------------------------*/