aboutsummaryrefslogtreecommitdiff
path: root/lib/select.c
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2007-03-10 12:11:21 +0000
committerDaniel Stenberg <daniel@haxx.se>2007-03-10 12:11:21 +0000
commitdbaf4f93615b0ad1f34b38ec9def0b30496658d7 (patch)
treeb9299f0d71a7593ffc31201f5955d3f59076b6f1 /lib/select.c
parent433575068c60d107beb566259f061a66e529b581 (diff)
- Bryan Henderson introduces two things:
1) the progress callback gets called more frequently (at times) 2) libcurl *might* call the callback when it receives a signal
Diffstat (limited to 'lib/select.c')
-rw-r--r--lib/select.c169
1 files changed, 150 insertions, 19 deletions
diff --git a/lib/select.c b/lib/select.c
index 4f8878401..a1e149a12 100644
--- a/lib/select.c
+++ b/lib/select.c
@@ -32,9 +32,7 @@
#include <sys/time.h>
#endif
-#ifndef HAVE_SELECT
-#error "We can't compile without select() support!"
-#endif
+#include <signal.h>
#ifdef __BEOS__
/* BeOS has FD_SET defined in socket.h */
@@ -53,6 +51,57 @@
/* Winsock and TPF sockets are not in range [0..FD_SETSIZE-1] */
+/* There are various ways to wait for a socket to be ready to give or take
+ * data. None of them are perfect.
+ *
+ * select() is available everywhere, but cannot take a file
+ * descriptor numerically greater than FD_SETSIZE but cannot be reliably
+ * interrupted by a signal.
+ *
+ * pselect() works with signals, but still has the file descriptor problem.
+ * And some older systems don't have it.
+ *
+ * poll() (and equivalently on Windows, WSAPoll()) can take any file
+ * descriptor, but has the signal problem. And some older systems
+ * don't have it.
+ *
+ * The signal issue is this: We would like to be able to avoid the
+ * wait if a signal has arrived since we last checked for it. All
+ * these methods terminate the wait (with EINTR) if a signal arrives
+ * while the waiting is underway, so it's just signals that happen
+ * shortly before the wait that are a problem. With pselect(), this
+ * is possible because it has the ability to simultaneously unblock
+ * signals _after_ the wait begins. So you just block signals, then
+ * check for arrival, then assuming no signals have arrived, call
+ * pselect() with an argument that says to unblock signals. Any
+ * signal that arrived after you blocked will thus interrupt the wait
+ * and pselect() returns immediately.
+ *
+ * Curl_pselect() is our compromise among these. We use poll()
+ * whenever it is available and select() otherwise. We emulate
+ * pselect-like signal behavior by unblocking signals just before
+ * calling poll() or select() and re-blocking after. This only
+ * _approximates_ pselect(), because there is a window in which a
+ * signal may arrive and we wait anyway.
+ *
+ * To reduce that window, we use pselect(), if it is available --
+ * with no file descriptors -- just before the poll() or select() in
+ * order to detect signals that arrived between when the caller
+ * blocked signals and when he called Curl_pselect().
+ *
+ * Curl_select() is for callers who want us to ignore caught signals and
+ * wait until a socket is ready or the timeout expires. We implement that
+ * simply as a loop around Curl_pselect().
+ *
+ * There is a way to add signal interruptibility to poll(), which we
+ * don't provide today: Let caller give us a file descriptor to add
+ * to our list of wait-for-readable file descriptors. Caller passes
+ * us the fd of a pipe. He doesn't block signals and his signal
+ * handler writes to the other end of that pipe. Therefore, a signal
+ * causes poll() to return, even if received before poll() was
+ * called.
+ */
+
#if defined(USE_WINSOCK) || defined(TPF)
#define VERIFY_SOCK(x) do { } while (0)
#else
@@ -66,18 +115,56 @@
#endif
/*
- * This is an internal function used for waiting for read or write
- * events on single file descriptors. It attempts to replace select()
- * in order to avoid limits with FD_SETSIZE.
+ * This function unblocks a set of signal classes momentarily, to allow any
+ * the process to receive any presently blocked signal. If there exists
+ * a handler for that, it will run now. If not, it will typically
+ * terminate the process.
*
- * Return values:
- * -1 = system call error
- * 0 = timeout
- * CSELECT_IN | CSELECT_OUT | CSELECT_ERR
+ * We return 1 if as a result of the unblocking, a signal was
+ * received, caught and handled. 0 otherwise.
+ *
+ * On a system that does not have pselect(), we always return 0, even if
+ * signals were received.
*/
-int Curl_select(curl_socket_t readfd, curl_socket_t writefd, int timeout_ms)
+int receive_signals(sigset_t * sigmask)
{
+#ifdef HAVE_PSELECT
+ struct timespec zeroTime = {0, 0};
+
+ /* Note that on older Linux, pselect() is imperfect -- the kernel doesn't
+ have a pselect() system call, so the GNU C Library implements it
+ with sigprocmask() followed by select(), which means the result is
+ the same as with the code below for systmes with no pselect() at all.
+ */
+ if (pselect(0, NULL, NULL, NULL, &zeroTime, sigmask) == 0)
+ return 0;
+ else
+ return 1;
+#else
+ sigset_t oldmask;
+
+ sigprocmask(SIG_SETMASK, sigmask, &oldmask);
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ return 0;
+#endif
+}
+
#if defined(HAVE_POLL_FINE) || defined(CURL_HAVE_WSAPOLL)
+ #define USE_POLL_FOR_SELECT 1
+#else
+ #if defined(HAVE_SELECT)
+ #define USE_POLL_FOR_SELECT 0
+ #else
+ #error "You don't appear to have either poll() or select()."
+ #endif
+#endif
+
+#if USE_POLL_FOR_SELECT
+
+static int select_with_poll(curl_socket_t readfd, curl_socket_t writefd,
+ int timeout_ms)
+{
struct pollfd pfd[2];
int num;
int r;
@@ -95,13 +182,11 @@ int Curl_select(curl_socket_t readfd, curl_socket_t writefd, int timeout_ms)
num++;
}
- do {
#ifdef CURL_HAVE_WSAPOLL
- r = WSAPoll(pfd, num, timeout_ms);
+ r = WSAPoll(pfd, num, timeout_ms);
#else
- r = poll(pfd, num, timeout_ms);
+ r = poll(pfd, num, timeout_ms);
#endif
- } while((r == -1) && (SOCKERRNO == EINTR));
if (r < 0)
return -1;
@@ -132,7 +217,13 @@ int Curl_select(curl_socket_t readfd, curl_socket_t writefd, int timeout_ms)
}
return ret;
-#else
+}
+
+#endif USE_POLL_FOR_SELECT
+
+static int select_with_select(curl_socket_t readfd, curl_socket_t writefd,
+ int timeout_ms)
+{
struct timeval timeout;
fd_set fds_read;
fd_set fds_write;
@@ -179,9 +270,7 @@ int Curl_select(curl_socket_t readfd, curl_socket_t writefd, int timeout_ms)
maxfd = writefd;
}
- do {
- r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, &timeout);
- } while((r == -1) && (SOCKERRNO == EINTR));
+ r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, &timeout);
if (r < 0)
return -1;
@@ -203,7 +292,49 @@ int Curl_select(curl_socket_t readfd, curl_socket_t writefd, int timeout_ms)
}
return ret;
+}
+
+/*
+ * This is an internal function used for waiting for read or write
+ * events on single file descriptors. It attempts to replace select()
+ * in order to avoid limits with FD_SETSIZE.
+ *
+ * Return values:
+ * -1 = system call error, including interrupted by signal
+ * 0 = timeout
+ * CSELECT_IN | CSELECT_OUT | CSELECT_ERR
+ */
+int Curl_pselect(curl_socket_t readfd, curl_socket_t writefd, int timeout_ms,
+ sigset_t * sigmask)
+{
+ int ret;
+ sigset_t oldmask;
+
+ if (sigmask && receive_signals(sigmask)) {
+ SET_SOCKERRNO(EINTR);
+ ret = -1;
+ } else {
+ if (sigmask)
+ sigprocmask(SIG_SETMASK, sigmask, &oldmask);
+#if USE_POLL_FOR_SELECT
+ ret = select_with_poll(readfd, writefd, timeout_ms);
+#else
+ ret = select_with_select(readfd, writefd, timeout_ms);
#endif
+ if (sigmask)
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+ }
+ return ret;
+}
+
+int Curl_select(curl_socket_t readfd, curl_socket_t writefd, int timeout_ms)
+{
+ int r;
+ do {
+ r = Curl_pselect(readfd, writefd, timeout_ms, NULL);
+ } while((r == -1) && (SOCKERRNO == EINTR));
+
+ return r;
}
/*