From f3c35e371cc70f1b6bc33f7faa904d37d1567eb3 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Sun, 17 Nov 2019 15:12:15 +0100 Subject: 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 --- docs/TODO | 8 -- docs/libcurl/Makefile.inc | 1 + docs/libcurl/curl_multi_poll.3 | 11 ++- docs/libcurl/curl_multi_wakeup.3 | 47 +++++++++ docs/libcurl/libcurl-errors.3 | 2 + docs/libcurl/symbols-in-versions | 1 + include/curl/multi.h | 10 ++ lib/multi.c | 114 +++++++++++++++++++++- lib/multihandle.h | 10 ++ lib/strerror.c | 3 + packages/OS400/curl.inc.in | 6 +- tests/data/Makefile.inc | 2 +- tests/data/test1135 | 1 + tests/data/test1538 | 3 +- tests/data/test1564 | 31 ++++++ tests/data/test1565 | 41 ++++++++ tests/libtest/Makefile.inc | 10 +- tests/libtest/lib1564.c | 142 +++++++++++++++++++++++++++ tests/libtest/lib1565.c | 204 +++++++++++++++++++++++++++++++++++++++ tests/libtest/test.h | 54 +++++++++++ 20 files changed, 684 insertions(+), 17 deletions(-) create mode 100644 docs/libcurl/curl_multi_wakeup.3 create mode 100644 tests/data/test1564 create mode 100644 tests/data/test1565 create mode 100644 tests/libtest/lib1564.c create mode 100644 tests/libtest/lib1565.c 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, , 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 + +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 @@ + + + +multi +wakeup + + + +# Server-side + + + +# Client-side + + +none + + +lib1564 + + +wakeup before poll with no easy handles + + + + + +# Verify data after the test has been "shot" + + + 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 @@ + + + +HTTP +HTTP GET +multi +multi-threaded +wakeup + + + +# Server-side + + +HTTP/1.1 200 OK +Content-Length: 3 + +OK + + + +# Client-side + + +http + + +lib1565 + + +wakeup from another thread + + +http://%HOSTIP:%HTTPPORT/1 + + + +# Verify data after the test has been "shot" + + + 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, , 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, , 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 +#include + +#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) { \ -- cgit v1.2.1