1
0
mirror of https://github.com/moparisthebest/curl synced 2024-12-23 00:28:48 -05:00

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
This commit is contained in:
Gergely Nagy 2019-11-17 15:12:15 +01:00 committed by Daniel Stenberg
parent 0a65febccf
commit f3c35e371c
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
20 changed files with 684 additions and 17 deletions

View File

@ -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

View File

@ -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 \

View File

@ -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)"

View File

@ -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)"

View File

@ -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.

View File

@ -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

View File

@ -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()
*

View File

@ -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;

View File

@ -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 */

View File

@ -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;
}

View File

@ -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

View File

@ -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 \
\

View File

@ -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,

View File

@ -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

31
tests/data/test1564 Normal file
View File

@ -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>

41
tests/data/test1565 Normal file
View File

@ -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>

View File

@ -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

142
tests/libtest/lib1564.c Normal file
View File

@ -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;
}

204
tests/libtest/lib1565.c Normal file
View File

@ -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

View File

@ -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) { \