summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGergely Nagy <ngg@tresorit.com>2019-11-17 15:12:15 +0100
committerDaniel Stenberg <daniel@haxx.se>2019-11-25 15:45:56 +0100
commitf3c35e371cc70f1b6bc33f7faa904d37d1567eb3 (patch)
tree3c7668dec5726aa510d7be282ff440c9ee370802
parent0a65febccf0e31bc987be3f90c01194804e61b77 (diff)
downloadcurl-f3c35e371cc70f1b6bc33f7faa904d37d1567eb3.tar.gz
multi: add curl_multi_wakeup()
This commit adds curl_multi_wakeup() which was previously in the TODO list under the curl_multi_unblock name. On some platforms and with some configurations this feature might not be available or can fail, in these cases a new error code (CURLM_WAKEUP_FAILURE) is returned from curl_multi_wakeup(). Fixes #4418 Closes #4608
-rw-r--r--docs/TODO8
-rw-r--r--docs/libcurl/Makefile.inc1
-rw-r--r--docs/libcurl/curl_multi_poll.311
-rw-r--r--docs/libcurl/curl_multi_wakeup.347
-rw-r--r--docs/libcurl/libcurl-errors.32
-rw-r--r--docs/libcurl/symbols-in-versions1
-rw-r--r--include/curl/multi.h10
-rwxr-xr-xlib/multi.c114
-rw-r--r--lib/multihandle.h10
-rw-r--r--lib/strerror.c3
-rw-r--r--packages/OS400/curl.inc.in6
-rw-r--r--tests/data/Makefile.inc2
-rw-r--r--tests/data/test11351
-rw-r--r--tests/data/test15383
-rw-r--r--tests/data/test156431
-rw-r--r--tests/data/test156541
-rw-r--r--tests/libtest/Makefile.inc10
-rw-r--r--tests/libtest/lib1564.c142
-rw-r--r--tests/libtest/lib1565.c204
-rw-r--r--tests/libtest/test.h54
20 files changed, 684 insertions, 17 deletions
diff --git a/docs/TODO b/docs/TODO
index 46b015c02..e75c1647b 100644
--- a/docs/TODO
+++ b/docs/TODO
@@ -51,7 +51,6 @@
2.4 Split connect and authentication process
2.5 Edge-triggered sockets should work
2.6 multi upkeep
- 2.7 curl_multi_unblock
3. Documentation
3.2 Provide cmake config-file
@@ -448,13 +447,6 @@
See https://github.com/curl/curl/issues/3199
-2.7 curl_multi_unblock
-
- A portable way to unblock curl_multi_wait from another thread.
-
- See https://github.com/curl/curl/issues/4418 and
- https://github.com/curl/curl/wiki/curl_multi_unblock
-
3. Documentation
3.2 Provide cmake config-file
diff --git a/docs/libcurl/Makefile.inc b/docs/libcurl/Makefile.inc
index bd88c9c38..e1185e729 100644
--- a/docs/libcurl/Makefile.inc
+++ b/docs/libcurl/Makefile.inc
@@ -54,6 +54,7 @@ man_MANS = \
curl_multi_socket_all.3 \
curl_multi_strerror.3 \
curl_multi_timeout.3 \
+ curl_multi_wakeup.3 \
curl_multi_wait.3 \
curl_share_cleanup.3 \
curl_share_init.3 \
diff --git a/docs/libcurl/curl_multi_poll.3 b/docs/libcurl/curl_multi_poll.3
index 9fc72c55d..bde80447a 100644
--- a/docs/libcurl/curl_multi_poll.3
+++ b/docs/libcurl/curl_multi_poll.3
@@ -48,10 +48,16 @@ total number of file descriptors on which interesting events occurred. This
number can include both libcurl internal descriptors as well as descriptors
provided in \fIextra_fds\fP.
+The \fIcurl_multi_wakeup(3)\fP function can be used from another thread to
+wake up this function and return faster. This is one of the details
+that makes this function different than \fIcurl_multi_wait(3)\fP which cannot
+be woken up this way.
+
If no extra file descriptors are provided and libcurl has no file descriptor
to offer to wait for, this function will instead wait during \fItimeout_ms\fP
milliseconds (or shorter if an internal timer indicates so). This is the
-detail that makes this function different than \fIcurl_multi_wait(3)\fP.
+other detail that makes this function different than
+\fIcurl_multi_wait(3)\fP.
This function is encouraged to be used instead of select(3) when using the
multi interface to allow applications to easier circumvent the common problem
@@ -107,4 +113,5 @@ CURLMcode type, general libcurl multi interface error code. See
.SH AVAILABILITY
This function was added in libcurl 7.66.0.
.SH "SEE ALSO"
-.BR curl_multi_fdset "(3), " curl_multi_perform "(3), " curl_multi_wait "(3)"
+.BR curl_multi_fdset "(3), " curl_multi_perform "(3), "
+.BR curl_multi_wait "(3), " curl_multi_wakeup "(3)"
diff --git a/docs/libcurl/curl_multi_wakeup.3 b/docs/libcurl/curl_multi_wakeup.3
new file mode 100644
index 000000000..01b462f2f
--- /dev/null
+++ b/docs/libcurl/curl_multi_wakeup.3
@@ -0,0 +1,47 @@
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * Project ___| | | | _ \| |
+.\" * / __| | | | |_) | |
+.\" * | (__| |_| | _ <| |___
+.\" * \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at https://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" **************************************************************************
+.TH curl_multi_wakeup 3 "17 Nov 2019" "libcurl 7.68.0" "libcurl Manual"
+.SH NAME
+curl_multi_wakeup - wakes up a sleeping curl_multi_poll call
+.SH SYNOPSIS
+#include <curl/curl.h>
+
+CURLMcode curl_multi_wakeup(CURLM *multi_handle);
+.ad
+.SH DESCRIPTION
+This function can be called from any thread and it wakes up a
+sleeping \fIcurl_multi_poll(3)\fP call that is currently (or will be)
+waiting for activity or a timeout.
+
+If the function is called when there is no \fIcurl_multi_poll(3)\fP call,
+it will cause the next call to return immediately.
+
+Calling this function only guarantees to wake up the current (or the next
+if there is no current) \fIcurl_multi_poll(3)\fP call, which means it is
+possible that multiple calls to this function will wake up the same waiting
+operation.
+
+This function has no effect on \fIcurl_multi_wait(3)\fP calls.
+.SH RETURN VALUE
+CURLMcode type, general libcurl multi interface error code.
+.SH "SEE ALSO"
+.BR curl_multi_poll "(3), " curl_multi_wait "(3)"
diff --git a/docs/libcurl/libcurl-errors.3 b/docs/libcurl/libcurl-errors.3
index 1f985da8e..0305af43e 100644
--- a/docs/libcurl/libcurl-errors.3
+++ b/docs/libcurl/libcurl-errors.3
@@ -297,6 +297,8 @@ An easy handle already added to a multi handle was attempted to get added a
second time. (Added in 7.32.1)
.IP "CURLM_RECURSIVE_API_CALL (8)"
An API function was called from inside a callback.
+.IP "CURLM_WAKEUP_FAILURE (9)"
+Wakeup is unavailable or failed.
.SH "CURLSHcode"
The "share" interface will return a CURLSHcode to indicate when an error has
occurred. Also consider \fIcurl_share_strerror(3)\fP.
diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
index f3131d2a2..d82439a5b 100644
--- a/docs/libcurl/symbols-in-versions
+++ b/docs/libcurl/symbols-in-versions
@@ -342,6 +342,7 @@ CURLM_INTERNAL_ERROR 7.9.6
CURLM_OK 7.9.6
CURLM_OUT_OF_MEMORY 7.9.6
CURLM_RECURSIVE_API_CALL 7.59.0
+CURLM_WAKEUP_FAILURE 7.68.0
CURLM_UNKNOWN_OPTION 7.15.4
CURLOPTTYPE_FUNCTIONPOINT 7.1
CURLOPTTYPE_LONG 7.1
diff --git a/include/curl/multi.h b/include/curl/multi.h
index b39218395..89f099259 100644
--- a/include/curl/multi.h
+++ b/include/curl/multi.h
@@ -72,6 +72,7 @@ typedef enum {
attempted to get added - again */
CURLM_RECURSIVE_API_CALL, /* an api function was called from inside a
callback */
+ CURLM_WAKEUP_FAILURE, /* wakeup is unavailable or failed */
CURLM_LAST
} CURLMcode;
@@ -187,6 +188,15 @@ CURL_EXTERN CURLMcode curl_multi_poll(CURLM *multi_handle,
int timeout_ms,
int *ret);
+/*
+ * Name: curl_multi_wakeup()
+ *
+ * Desc: wakes up a sleeping curl_multi_poll call.
+ *
+ * Returns: CURLMcode type, general multi error code.
+ */
+CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *multi_handle);
+
/*
* Name: curl_multi_perform()
*
diff --git a/lib/multi.c b/lib/multi.c
index 9faad0e2e..f30e41a65 100755
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -46,6 +46,7 @@
#include "connect.h"
#include "http_proxy.h"
#include "http2.h"
+#include "socketpair.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
@@ -367,6 +368,21 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */
/* -1 means it not set by user, use the default value */
multi->maxconnects = -1;
+
+#ifdef ENABLE_WAKEUP
+ if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, multi->wakeup_pair) < 0) {
+ multi->wakeup_pair[0] = CURL_SOCKET_BAD;
+ multi->wakeup_pair[1] = CURL_SOCKET_BAD;
+ }
+ else if(curlx_nonblock(multi->wakeup_pair[0], TRUE) < 0 ||
+ curlx_nonblock(multi->wakeup_pair[1], TRUE) < 0) {
+ sclose(multi->wakeup_pair[0]);
+ sclose(multi->wakeup_pair[1]);
+ multi->wakeup_pair[0] = CURL_SOCKET_BAD;
+ multi->wakeup_pair[1] = CURL_SOCKET_BAD;
+ }
+#endif
+
return multi;
error:
@@ -1005,7 +1021,8 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
unsigned int extra_nfds,
int timeout_ms,
int *ret,
- bool extrawait) /* when no socket, wait */
+ bool extrawait, /* when no socket, wait */
+ bool use_wakeup)
{
struct Curl_easy *data;
curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE];
@@ -1059,6 +1076,12 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
curlfds = nfds; /* number of internal file descriptors */
nfds += extra_nfds; /* add the externally provided ones */
+#ifdef ENABLE_WAKEUP
+ if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) {
+ ++nfds;
+ }
+#endif
+
if(nfds > NUM_POLLS_ON_STACK) {
/* 'nfds' is a 32 bit value and 'struct pollfd' is typically 8 bytes
big, so at 2^29 sockets this value might wrap. When a process gets
@@ -1117,6 +1140,14 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
++nfds;
}
+#ifdef ENABLE_WAKEUP
+ if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) {
+ ufds[nfds].fd = multi->wakeup_pair[0];
+ ufds[nfds].events = POLLIN;
+ ++nfds;
+ }
+#endif
+
if(nfds) {
int pollrc;
/* wait... */
@@ -1140,6 +1171,29 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
extra_fds[i].revents = mask;
}
+
+#ifdef ENABLE_WAKEUP
+ if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) {
+ if(ufds[curlfds + extra_nfds].revents & POLLIN) {
+ char buf[64];
+ while(1) {
+ /* the reading socket is non-blocking, try to read
+ data from it until it receives an error (except EINTR).
+ In normal cases it will get EAGAIN or EWOULDBLOCK
+ when there is no more data, breaking the loop. */
+ if(sread(multi->wakeup_pair[0], buf, sizeof(buf)) < 0) {
+#ifndef USE_WINSOCK
+ if(EINTR == SOCKERRNO)
+ continue;
+#endif
+ break;
+ }
+ }
+ /* do not count the wakeup socket into the returned value */
+ retcode--;
+ }
+ }
+#endif
}
}
@@ -1174,7 +1228,8 @@ CURLMcode curl_multi_wait(struct Curl_multi *multi,
int timeout_ms,
int *ret)
{
- return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, FALSE);
+ return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, FALSE,
+ FALSE);
}
CURLMcode curl_multi_poll(struct Curl_multi *multi,
@@ -1183,7 +1238,55 @@ CURLMcode curl_multi_poll(struct Curl_multi *multi,
int timeout_ms,
int *ret)
{
- return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, TRUE);
+ return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, TRUE,
+ TRUE);
+}
+
+CURLMcode curl_multi_wakeup(struct Curl_multi *multi)
+{
+ /* this function is usually called from another thread,
+ it has to be careful only to access parts of the
+ Curl_multi struct that are constant */
+
+ /* GOOD_MULTI_HANDLE can be safely called */
+ if(!GOOD_MULTI_HANDLE(multi))
+ return CURLM_BAD_HANDLE;
+
+#ifdef ENABLE_WAKEUP
+ /* the wakeup_pair variable is only written during init and cleanup,
+ making it safe to access from another thread after the init part
+ and before cleanup */
+ if(multi->wakeup_pair[1] != CURL_SOCKET_BAD) {
+ char buf[1];
+ buf[0] = 1;
+ while(1) {
+ /* swrite() is not thread-safe in general, because concurrent calls
+ can have their messages interleaved, but in this case the content
+ of the messages does not matter, which makes it ok to call.
+
+ The write socket is set to non-blocking, this way this function
+ cannot block, making it safe to call even from the same thread
+ that will call Curl_multi_wait(). If swrite() returns that it
+ would block, it's considered successful because it means that
+ previous calls to this function will wake up the poll(). */
+ if(swrite(multi->wakeup_pair[1], buf, sizeof(buf)) < 0) {
+ int err = SOCKERRNO;
+ int return_success;
+#ifdef USE_WINSOCK
+ return_success = WSAEWOULDBLOCK == err;
+#else
+ if(EINTR == err)
+ continue;
+ return_success = EWOULDBLOCK == err || EAGAIN == err;
+#endif
+ if(!return_success)
+ return CURLM_WAKEUP_FAILURE;
+ }
+ return CURLM_OK;
+ }
+ }
+#endif
+ return CURLM_WAKEUP_FAILURE;
}
/*
@@ -2309,6 +2412,11 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
Curl_hash_destroy(&multi->hostcache);
Curl_psl_destroy(&multi->psl);
+
+#ifdef ENABLE_WAKEUP
+ sclose(multi->wakeup_pair[0]);
+ sclose(multi->wakeup_pair[1]);
+#endif
free(multi);
return CURLM_OK;
diff --git a/lib/multihandle.h b/lib/multihandle.h
index b65bd9638..a26fb619a 100644
--- a/lib/multihandle.h
+++ b/lib/multihandle.h
@@ -24,6 +24,7 @@
#include "conncache.h"
#include "psl.h"
+#include "socketpair.h"
struct Curl_message {
struct curl_llist_element list;
@@ -66,6 +67,10 @@ typedef enum {
#define CURLPIPE_ANY (CURLPIPE_MULTIPLEX)
+#if defined(USE_SOCKETPAIR) && !defined(USE_BLOCKING_SOCKETS)
+#define ENABLE_WAKEUP
+#endif
+
/* This is the struct known as CURLM on the outside */
struct Curl_multi {
/* First a simple identifier to easier detect if a user mix up
@@ -134,6 +139,11 @@ struct Curl_multi {
previous callback */
bool in_callback; /* true while executing a callback */
long max_concurrent_streams; /* max concurrent streams client to support */
+
+#ifdef ENABLE_WAKEUP
+ curl_socket_t wakeup_pair[2]; /* socketpair() used for wakeup
+ 0 is used for read, 1 is used for write */
+#endif
};
#endif /* HEADER_CURL_MULTIHANDLE_H */
diff --git a/lib/strerror.c b/lib/strerror.c
index ba2e7a6f9..baf5451ae 100644
--- a/lib/strerror.c
+++ b/lib/strerror.c
@@ -389,6 +389,9 @@ curl_multi_strerror(CURLMcode error)
case CURLM_RECURSIVE_API_CALL:
return "API function called from within callback";
+ case CURLM_WAKEUP_FAILURE:
+ return "Wakeup is unavailable or failed";
+
case CURLM_LAST:
break;
}
diff --git a/packages/OS400/curl.inc.in b/packages/OS400/curl.inc.in
index 8be6c8986..9a44eae49 100644
--- a/packages/OS400/curl.inc.in
+++ b/packages/OS400/curl.inc.in
@@ -1804,7 +1804,11 @@
d c 6
d CURLM_ADDED_ALREADY...
d c 7
- d CURLM_LAST c 8
+ d CURLM_RECURSIVE_API_CALL...
+ d c 8
+ d CURLM_WAKEUP_FAILURE...
+ d c 9
+ d CURLM_LAST c 10
*
d CURLMSG s 10i 0 based(######ptr######) Enum
d CURLMSG_NONE c 0
diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
index 23215655b..c45bced5b 100644
--- a/tests/data/Makefile.inc
+++ b/tests/data/Makefile.inc
@@ -179,7 +179,7 @@ test1525 test1526 test1527 test1528 test1529 test1530 test1531 test1532 \
test1533 test1534 test1535 test1536 test1537 test1538 \
test1540 test1541 \
test1550 test1551 test1552 test1553 test1554 test1555 test1556 test1557 \
-test1558 test1559 test1560 test1561 test1562 test1563 \
+test1558 test1559 test1560 test1561 test1562 test1563 test1564 test1565 \
\
test1590 test1591 test1592 test1593 test1594 test1595 test1596 \
\
diff --git a/tests/data/test1135 b/tests/data/test1135
index eca6860fb..37a55427f 100644
--- a/tests/data/test1135
+++ b/tests/data/test1135
@@ -92,6 +92,7 @@ CURL_EXTERN CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
CURL_EXTERN CURLMcode curl_multi_fdset(CURLM *multi_handle,
CURL_EXTERN CURLMcode curl_multi_wait(CURLM *multi_handle,
CURL_EXTERN CURLMcode curl_multi_poll(CURLM *multi_handle,
+CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *multi_handle);
CURL_EXTERN CURLMcode curl_multi_perform(CURLM *multi_handle,
CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle);
CURL_EXTERN CURLMsg *curl_multi_info_read(CURLM *multi_handle,
diff --git a/tests/data/test1538 b/tests/data/test1538
index 36f53040b..3b22ebc27 100644
--- a/tests/data/test1538
+++ b/tests/data/test1538
@@ -139,7 +139,8 @@ m5: Invalid socket argument
m6: Unknown option
m7: The easy handle is already added to a multi handle
m8: API function called from within callback
-m9: Unknown error
+m9: Wakeup is unavailable or failed
+m10: Unknown error
s0: No error
s1: Unknown share option
s2: Share currently in use
diff --git a/tests/data/test1564 b/tests/data/test1564
new file mode 100644
index 000000000..279665bd1
--- /dev/null
+++ b/tests/data/test1564
@@ -0,0 +1,31 @@
+<testcase>
+<info>
+<keywords>
+multi
+wakeup
+</keywords>
+</info>
+
+# Server-side
+<reply>
+</reply>
+
+# Client-side
+<client>
+<server>
+none
+</server>
+<tool>
+lib1564
+</tool>
+<name>
+wakeup before poll with no easy handles
+</name>
+<command>
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+</verify>
+</testcase>
diff --git a/tests/data/test1565 b/tests/data/test1565
new file mode 100644
index 000000000..f554e0f34
--- /dev/null
+++ b/tests/data/test1565
@@ -0,0 +1,41 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+multi
+multi-threaded
+wakeup
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+Content-Length: 3
+
+OK
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<tool>
+lib1565
+</tool>
+<name>
+wakeup from another thread
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/1
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+</verify>
+</testcase>
diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc
index 9ba72d7de..374a66747 100644
--- a/tests/libtest/Makefile.inc
+++ b/tests/libtest/Makefile.inc
@@ -31,7 +31,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \
lib1534 lib1535 lib1536 lib1537 lib1538 \
lib1540 lib1541 \
lib1550 lib1551 lib1552 lib1553 lib1554 lib1555 lib1556 lib1557 \
- lib1558 lib1559 lib1560 \
+ lib1558 lib1559 lib1560 lib1564 lib1565 \
lib1591 lib1592 lib1593 lib1594 lib1596 \
lib1900 lib1905 lib1906 lib1907 \
lib2033
@@ -536,6 +536,14 @@ lib1559_LDADD = $(TESTUTIL_LIBS)
lib1560_SOURCES = lib1560.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib1560_LDADD = $(TESTUTIL_LIBS)
+lib1564_SOURCES = lib1564.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib1564_LDADD = $(TESTUTIL_LIBS)
+lib1564_CPPFLAGS = $(AM_CPPFLAGS)
+
+lib1565_SOURCES = lib1565.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib1565_LDADD = $(TESTUTIL_LIBS)
+lib1565_CPPFLAGS = $(AM_CPPFLAGS)
+
lib1591_SOURCES = lib1591.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib1591_LDADD = $(TESTUTIL_LIBS)
lib1591_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1591
diff --git a/tests/libtest/lib1564.c b/tests/libtest/lib1564.c
new file mode 100644
index 000000000..225c8c6d7
--- /dev/null
+++ b/tests/libtest/lib1564.c
@@ -0,0 +1,142 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include "test.h"
+
+#include "testutil.h"
+#include "warnless.h"
+#include "memdebug.h"
+
+#define TEST_HANG_TIMEOUT 60 * 1000
+#define WAKEUP_NUM 1234567
+
+int test(char *URL)
+{
+ CURLM *multi = NULL;
+ int numfds;
+ int i;
+ int res = 0;
+ struct timeval time_before_wait, time_after_wait;
+
+ (void)URL;
+
+ start_test_timing();
+
+ global_init(CURL_GLOBAL_ALL);
+
+ multi_init(multi);
+
+ /* no wakeup */
+
+ time_before_wait = tutil_tvnow();
+ multi_poll(multi, NULL, 0, 1000, &numfds);
+ time_after_wait = tutil_tvnow();
+
+ if(tutil_tvdiff(time_after_wait, time_before_wait) < 500) {
+ fprintf(stderr, "%s:%d curl_multi_poll returned too early\n",
+ __FILE__, __LINE__);
+ res = TEST_ERR_MAJOR_BAD;
+ goto test_cleanup;
+ }
+
+ abort_on_test_timeout();
+
+ /* try a single wakeup */
+
+ multi_wakeup(multi);
+
+ time_before_wait = tutil_tvnow();
+ multi_poll(multi, NULL, 0, 1000, &numfds);
+ time_after_wait = tutil_tvnow();
+
+ if(tutil_tvdiff(time_after_wait, time_before_wait) > 500) {
+ fprintf(stderr, "%s:%d curl_multi_poll returned too late\n",
+ __FILE__, __LINE__);
+ res = TEST_ERR_MAJOR_BAD;
+ goto test_cleanup;
+ }
+
+ abort_on_test_timeout();
+
+ /* previous wakeup should not wake up this */
+
+ time_before_wait = tutil_tvnow();
+ multi_poll(multi, NULL, 0, 1000, &numfds);
+ time_after_wait = tutil_tvnow();
+
+ if(tutil_tvdiff(time_after_wait, time_before_wait) < 500) {
+ fprintf(stderr, "%s:%d curl_multi_poll returned too early\n",
+ __FILE__, __LINE__);
+ res = TEST_ERR_MAJOR_BAD;
+ goto test_cleanup;
+ }
+
+ abort_on_test_timeout();
+
+ /* try lots of wakeup */
+
+ for(i = 0; i < WAKEUP_NUM; ++i)
+ multi_wakeup(multi);
+
+ time_before_wait = tutil_tvnow();
+ multi_poll(multi, NULL, 0, 1000, &numfds);
+ time_after_wait = tutil_tvnow();
+
+ if(tutil_tvdiff(time_after_wait, time_before_wait) > 500) {
+ fprintf(stderr, "%s:%d curl_multi_poll returned too late\n",
+ __FILE__, __LINE__);
+ res = TEST_ERR_MAJOR_BAD;
+ goto test_cleanup;
+ }
+
+ abort_on_test_timeout();
+
+#if !defined(WIN32) && !defined(_WIN32) && !defined(__WIN32__) \
+ && !defined(__CYGWIN__)
+ /* Even lots of previous wakeups should not wake up this.
+
+ On Windows (particularly when using MinGW), the socketpair
+ used for curl_multi_wakeup() is really asynchronous,
+ meaning when it's called a lot, it can take some time
+ before all of the data can be read. Sometimes it can wake
+ up more than one curl_multi_poll() call. */
+
+ time_before_wait = tutil_tvnow();
+ multi_poll(multi, NULL, 0, 1000, &numfds);
+ time_after_wait = tutil_tvnow();
+
+ if(tutil_tvdiff(time_after_wait, time_before_wait) < 500) {
+ fprintf(stderr, "%s:%d curl_multi_poll returned too early\n",
+ __FILE__, __LINE__);
+ res = TEST_ERR_MAJOR_BAD;
+ goto test_cleanup;
+ }
+
+ abort_on_test_timeout();
+#endif
+
+test_cleanup:
+
+ curl_multi_cleanup(multi);
+ curl_global_cleanup();
+
+ return res;
+}
diff --git a/tests/libtest/lib1565.c b/tests/libtest/lib1565.c
new file mode 100644
index 000000000..b2fa40aaa
--- /dev/null
+++ b/tests/libtest/lib1565.c
@@ -0,0 +1,204 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+#include "test.h"
+
+#include "testutil.h"
+#include "warnless.h"
+#include "memdebug.h"
+
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#include <unistd.h>
+
+#define TEST_HANG_TIMEOUT 60 * 1000
+#define CONN_NUM 3
+#define TIME_BETWEEN_START_SECS 2
+
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+static CURL *pending_handles[CONN_NUM];
+static int pending_num = 0;
+static int test_failure = 0;
+
+static CURLM *multi = NULL;
+static const char *url;
+
+static void *run_thread(void *ptr)
+{
+ CURL *easy = NULL;
+ int res = 0;
+ int i;
+
+ (void)ptr;
+
+ for(i = 0; i < CONN_NUM; i++) {
+ sleep(TIME_BETWEEN_START_SECS);
+
+ easy_init(easy);
+
+ easy_setopt(easy, CURLOPT_URL, url);
+ easy_setopt(easy, CURLOPT_VERBOSE, 0L);
+
+ pthread_mutex_lock(&lock);
+
+ if(test_failure) {
+ pthread_mutex_unlock(&lock);
+ goto test_cleanup;
+ }
+
+ pending_handles[pending_num] = easy;
+ pending_num++;
+ easy = NULL;
+
+ pthread_mutex_unlock(&lock);
+
+ multi_wakeup(multi);
+ }
+
+test_cleanup:
+
+ curl_easy_cleanup(easy);
+
+ pthread_mutex_lock(&lock);
+
+ if(!test_failure)
+ test_failure = res;
+
+ pthread_mutex_unlock(&lock);
+
+ return NULL;
+}
+
+int test(char *URL)
+{
+ int still_running;
+ int num;
+ int i;
+ int res = 0;
+ CURL *started_handles[CONN_NUM];
+ int started_num = 0;
+ int finished_num = 0;
+ pthread_t tid = 0;
+ struct CURLMsg *message;
+
+ start_test_timing();
+
+ global_init(CURL_GLOBAL_ALL);
+
+ multi_init(multi);
+
+ url = URL;
+
+ res = pthread_create(&tid, NULL, run_thread, NULL);
+ if(0 != res) {
+ fprintf(stderr, "%s:%d Couldn't create thread, errno %d\n",
+ __FILE__, __LINE__, res);
+ goto test_cleanup;
+ }
+
+ while(1) {
+ multi_perform(multi, &still_running);
+
+ abort_on_test_timeout();
+
+ while((message = curl_multi_info_read(multi, &num)) != NULL) {
+ if(message->msg == CURLMSG_DONE) {
+ res = message->data.result;
+ if(res)
+ goto test_cleanup;
+ multi_remove_handle(multi, message->easy_handle);
+ finished_num++;
+ }
+ else {
+ fprintf(stderr, "%s:%d Got an unexpected message from curl: %i\n",
+ __FILE__, __LINE__, (int)message->msg);
+ res = TEST_ERR_MAJOR_BAD;
+ goto test_cleanup;
+ }
+
+ abort_on_test_timeout();
+ }
+
+ if(CONN_NUM == finished_num)
+ break;
+
+ multi_poll(multi, NULL, 0, TEST_HANG_TIMEOUT, &num);
+
+ abort_on_test_timeout();
+
+ pthread_mutex_lock(&lock);
+
+ while(pending_num > 0) {
+ res_multi_add_handle(multi, pending_handles[pending_num - 1]);
+ if(res) {
+ pthread_mutex_unlock(&lock);
+ goto test_cleanup;
+ }
+
+ started_handles[started_num] = pending_handles[pending_num - 1];
+ started_num++;
+ pending_num--;
+ }
+
+ pthread_mutex_unlock(&lock);
+
+ abort_on_test_timeout();
+ }
+
+ if(CONN_NUM != started_num) {
+ fprintf(stderr, "%s:%d Not all connections started: %d of %d\n",
+ __FILE__, __LINE__, started_num, CONN_NUM);
+ goto test_cleanup;
+ }
+
+ if(CONN_NUM != finished_num) {
+ fprintf(stderr, "%s:%d Not all connections finished: %d of %d\n",
+ __FILE__, __LINE__, started_num, CONN_NUM);
+ goto test_cleanup;
+ }
+
+test_cleanup:
+
+ pthread_mutex_lock(&lock);
+ if(!test_failure)
+ test_failure = res;
+ pthread_mutex_unlock(&lock);
+
+ if(0 != tid)
+ pthread_join(tid, NULL);
+
+ curl_multi_cleanup(multi);
+ for(i = 0; i < pending_num; i++)
+ curl_easy_cleanup(pending_handles[i]);
+ for(i = 0; i < started_num; i++)
+ curl_easy_cleanup(started_handles[i]);
+ curl_global_cleanup();
+
+ return test_failure;
+}
+
+#else /* without pthread, this test doesn't work */
+int test(char *URL)
+{
+ (void)URL;
+ return 0;
+}
+#endif
diff --git a/tests/libtest/test.h b/tests/libtest/test.h
index bb1acca0e..3c8323de4 100644
--- a/tests/libtest/test.h
+++ b/tests/libtest/test.h
@@ -355,6 +355,60 @@ extern int unitfail;
/* ---------------------------------------------------------------- */
+#define exe_multi_poll(A,B,C,D,E,Y,Z) do { \
+ CURLMcode ec; \
+ if((ec = curl_multi_poll((A), (B), (C), (D), (E))) != CURLM_OK) { \
+ fprintf(stderr, "%s:%d curl_multi_poll() failed, " \
+ "with code %d (%s)\n", \
+ (Y), (Z), (int)ec, curl_multi_strerror(ec)); \
+ res = (int)ec; \
+ } \
+ else if(*((E)) < 0) { \
+ fprintf(stderr, "%s:%d curl_multi_poll() succeeded, " \
+ "but returned invalid numfds value (%d)\n", \
+ (Y), (Z), (int)*((E))); \
+ res = TEST_ERR_NUM_HANDLES; \
+ } \
+} WHILE_FALSE
+
+#define res_multi_poll(A, B, C, D, E) \
+ exe_multi_poll((A), (B), (C), (D), (E), (__FILE__), (__LINE__))
+
+#define chk_multi_poll(A, B, C, D, E, Y, Z) do { \
+ exe_multi_poll((A), (B), (C), (D), (E), (Y), (Z)); \
+ if(res) \
+ goto test_cleanup; \
+} WHILE_FALSE
+
+#define multi_poll(A, B, C, D, E) \
+ chk_multi_poll((A), (B), (C), (D), (E), (__FILE__), (__LINE__))
+
+/* ---------------------------------------------------------------- */
+
+#define exe_multi_wakeup(A,Y,Z) do { \
+ CURLMcode ec; \
+ if((ec = curl_multi_wakeup((A))) != CURLM_OK) { \
+ fprintf(stderr, "%s:%d curl_multi_wakeup() failed, " \
+ "with code %d (%s)\n", \
+ (Y), (Z), (int)ec, curl_multi_strerror(ec)); \
+ res = (int)ec; \
+ } \
+} WHILE_FALSE
+
+#define res_multi_wakeup(A) \
+ exe_multi_wakeup((A), (__FILE__), (__LINE__))
+
+#define chk_multi_wakeup(A, Y, Z) do { \
+ exe_multi_wakeup((A), (Y), (Z)); \
+ if(res) \
+ goto test_cleanup; \
+} WHILE_FALSE
+
+#define multi_wakeup(A) \
+ chk_multi_wakeup((A), (__FILE__), (__LINE__))
+
+/* ---------------------------------------------------------------- */
+
#define exe_select_test(A, B, C, D, E, Y, Z) do { \
int ec; \
if(select_wrapper((A), (B), (C), (D), (E)) == -1) { \