Skip to content

Commit

Permalink
add curl_multi_wakeup()
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ngg committed Nov 24, 2019
1 parent 74f441c commit f6262b7
Show file tree
Hide file tree
Showing 21 changed files with 684 additions and 18 deletions.
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ build_script:
..\builds\libcurl-vc15-x64-%PATHPART%-dll-ssl-dll-ipv6-sspi\bin\curl.exe -V
) else (
if %BUILD_SYSTEM%==autotools (
bash.exe -e -l -c "cd /c/projects/curl && ./buildconf && ./configure %CONFIG_ARGS% && make && make examples && cd tests && make"
bash.exe -e -l -c "cd /c/projects/curl && ./buildconf && ./configure %CONFIG_ARGS% && gcc -dM -E - < /dev/null && make && make examples && cd tests && make"
)))))

test_script:
Expand Down
8 changes: 0 additions & 8 deletions docs/TODO
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/libcurl/Makefile.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
11 changes: 9 additions & 2 deletions docs/libcurl/curl_multi_poll.3
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)"
47 changes: 47 additions & 0 deletions docs/libcurl/curl_multi_wakeup.3
Original file line number Diff line number Diff line change
@@ -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)"
2 changes: 2 additions & 0 deletions docs/libcurl/libcurl-errors.3
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions docs/libcurl/symbols-in-versions
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions include/curl/multi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()
*
Expand Down
114 changes: 111 additions & 3 deletions lib/multi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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... */
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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,
Expand All @@ -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;
}

/*
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions lib/multihandle.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#include "conncache.h"
#include "psl.h"
#include "socketpair.h"

struct Curl_message {
struct curl_llist_element list;
Expand Down Expand Up @@ -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
Expand Down Expand 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 */
3 changes: 3 additions & 0 deletions lib/strerror.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
6 changes: 5 additions & 1 deletion packages/OS400/curl.inc.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/data/Makefile.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
\
Expand Down
Loading

0 comments on commit f6262b7

Please sign in to comment.