Skip to content

Commit e9e03f1

Browse files
committed
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 curl#4418
1 parent 0a65feb commit e9e03f1

20 files changed

+684
-17
lines changed

docs/TODO

-8
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
2.4 Split connect and authentication process
5252
2.5 Edge-triggered sockets should work
5353
2.6 multi upkeep
54-
2.7 curl_multi_unblock
5554

5655
3. Documentation
5756
3.2 Provide cmake config-file
@@ -448,13 +447,6 @@
448447

449448
See https://github.com/curl/curl/issues/3199
450449

451-
2.7 curl_multi_unblock
452-
453-
A portable way to unblock curl_multi_wait from another thread.
454-
455-
See https://github.com/curl/curl/issues/4418 and
456-
https://github.com/curl/curl/wiki/curl_multi_unblock
457-
458450
3. Documentation
459451

460452
3.2 Provide cmake config-file

docs/libcurl/Makefile.inc

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ man_MANS = \
5454
curl_multi_socket_all.3 \
5555
curl_multi_strerror.3 \
5656
curl_multi_timeout.3 \
57+
curl_multi_wakeup.3 \
5758
curl_multi_wait.3 \
5859
curl_share_cleanup.3 \
5960
curl_share_init.3 \

docs/libcurl/curl_multi_poll.3

+9-2
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,16 @@ total number of file descriptors on which interesting events occurred. This
4848
number can include both libcurl internal descriptors as well as descriptors
4949
provided in \fIextra_fds\fP.
5050

51+
The \fIcurl_multi_wakeup(3)\fP function can be used from another thread to
52+
wake up this function and return faster. This is one of the details
53+
that makes this function different than \fIcurl_multi_wait(3)\fP which cannot
54+
be woken up this way.
55+
5156
If no extra file descriptors are provided and libcurl has no file descriptor
5257
to offer to wait for, this function will instead wait during \fItimeout_ms\fP
5358
milliseconds (or shorter if an internal timer indicates so). This is the
54-
detail that makes this function different than \fIcurl_multi_wait(3)\fP.
59+
other detail that makes this function different than
60+
\fIcurl_multi_wait(3)\fP.
5561

5662
This function is encouraged to be used instead of select(3) when using the
5763
multi interface to allow applications to easier circumvent the common problem
@@ -107,4 +113,5 @@ CURLMcode type, general libcurl multi interface error code. See
107113
.SH AVAILABILITY
108114
This function was added in libcurl 7.66.0.
109115
.SH "SEE ALSO"
110-
.BR curl_multi_fdset "(3), " curl_multi_perform "(3), " curl_multi_wait "(3)"
116+
.BR curl_multi_fdset "(3), " curl_multi_perform "(3), "
117+
.BR curl_multi_wait "(3), " curl_multi_wakeup "(3)"

docs/libcurl/curl_multi_wakeup.3

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
.\" **************************************************************************
2+
.\" * _ _ ____ _
3+
.\" * Project ___| | | | _ \| |
4+
.\" * / __| | | | |_) | |
5+
.\" * | (__| |_| | _ <| |___
6+
.\" * \___|\___/|_| \_\_____|
7+
.\" *
8+
.\" * Copyright (C) 1998 - 2019, Daniel Stenberg, <[email protected]>, et al.
9+
.\" *
10+
.\" * This software is licensed as described in the file COPYING, which
11+
.\" * you should have received as part of this distribution. The terms
12+
.\" * are also available at https://curl.haxx.se/docs/copyright.html.
13+
.\" *
14+
.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15+
.\" * copies of the Software, and permit persons to whom the Software is
16+
.\" * furnished to do so, under the terms of the COPYING file.
17+
.\" *
18+
.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19+
.\" * KIND, either express or implied.
20+
.\" *
21+
.\" **************************************************************************
22+
.TH curl_multi_wakeup 3 "17 Nov 2019" "libcurl 7.68.0" "libcurl Manual"
23+
.SH NAME
24+
curl_multi_wakeup - wakes up a sleeping curl_multi_poll call
25+
.SH SYNOPSIS
26+
#include <curl/curl.h>
27+
28+
CURLMcode curl_multi_wakeup(CURLM *multi_handle);
29+
.ad
30+
.SH DESCRIPTION
31+
This function can be called from any thread and it wakes up a
32+
sleeping \fIcurl_multi_poll(3)\fP call that is currently (or will be)
33+
waiting for activity or a timeout.
34+
35+
If the function is called when there is no \fIcurl_multi_poll(3)\fP call,
36+
it will cause the next call to return immediately.
37+
38+
Calling this function only guarantees to wake up the current (or the next
39+
if there is no current) \fIcurl_multi_poll(3)\fP call, which means it is
40+
possible that multiple calls to this function will wake up the same waiting
41+
operation.
42+
43+
This function has no effect on \fIcurl_multi_wait(3)\fP calls.
44+
.SH RETURN VALUE
45+
CURLMcode type, general libcurl multi interface error code.
46+
.SH "SEE ALSO"
47+
.BR curl_multi_poll "(3), " curl_multi_wait "(3)"

docs/libcurl/libcurl-errors.3

+2
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ An easy handle already added to a multi handle was attempted to get added a
297297
second time. (Added in 7.32.1)
298298
.IP "CURLM_RECURSIVE_API_CALL (8)"
299299
An API function was called from inside a callback.
300+
.IP "CURLM_WAKEUP_FAILURE (9)"
301+
Wakeup is unavailable or failed.
300302
.SH "CURLSHcode"
301303
The "share" interface will return a CURLSHcode to indicate when an error has
302304
occurred. Also consider \fIcurl_share_strerror(3)\fP.

docs/libcurl/symbols-in-versions

+1
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ CURLM_INTERNAL_ERROR 7.9.6
342342
CURLM_OK 7.9.6
343343
CURLM_OUT_OF_MEMORY 7.9.6
344344
CURLM_RECURSIVE_API_CALL 7.59.0
345+
CURLM_WAKEUP_FAILURE 7.68.0
345346
CURLM_UNKNOWN_OPTION 7.15.4
346347
CURLOPTTYPE_FUNCTIONPOINT 7.1
347348
CURLOPTTYPE_LONG 7.1

include/curl/multi.h

+10
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ typedef enum {
7272
attempted to get added - again */
7373
CURLM_RECURSIVE_API_CALL, /* an api function was called from inside a
7474
callback */
75+
CURLM_WAKEUP_FAILURE, /* wakeup is unavailable or failed */
7576
CURLM_LAST
7677
} CURLMcode;
7778

@@ -187,6 +188,15 @@ CURL_EXTERN CURLMcode curl_multi_poll(CURLM *multi_handle,
187188
int timeout_ms,
188189
int *ret);
189190

191+
/*
192+
* Name: curl_multi_wakeup()
193+
*
194+
* Desc: wakes up a sleeping curl_multi_poll call.
195+
*
196+
* Returns: CURLMcode type, general multi error code.
197+
*/
198+
CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *multi_handle);
199+
190200
/*
191201
* Name: curl_multi_perform()
192202
*

lib/multi.c

+111-3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include "connect.h"
4747
#include "http_proxy.h"
4848
#include "http2.h"
49+
#include "socketpair.h"
4950
/* The last 3 #include files should be in this order */
5051
#include "curl_printf.h"
5152
#include "curl_memory.h"
@@ -367,6 +368,21 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */
367368

368369
/* -1 means it not set by user, use the default value */
369370
multi->maxconnects = -1;
371+
372+
#ifdef ENABLE_WAKEUP
373+
if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, multi->wakeup_pair) < 0) {
374+
multi->wakeup_pair[0] = CURL_SOCKET_BAD;
375+
multi->wakeup_pair[1] = CURL_SOCKET_BAD;
376+
}
377+
else if(curlx_nonblock(multi->wakeup_pair[0], TRUE) < 0 ||
378+
curlx_nonblock(multi->wakeup_pair[1], TRUE) < 0) {
379+
sclose(multi->wakeup_pair[0]);
380+
sclose(multi->wakeup_pair[1]);
381+
multi->wakeup_pair[0] = CURL_SOCKET_BAD;
382+
multi->wakeup_pair[1] = CURL_SOCKET_BAD;
383+
}
384+
#endif
385+
370386
return multi;
371387

372388
error:
@@ -1005,7 +1021,8 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
10051021
unsigned int extra_nfds,
10061022
int timeout_ms,
10071023
int *ret,
1008-
bool extrawait) /* when no socket, wait */
1024+
bool extrawait, /* when no socket, wait */
1025+
bool use_wakeup)
10091026
{
10101027
struct Curl_easy *data;
10111028
curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE];
@@ -1059,6 +1076,12 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
10591076
curlfds = nfds; /* number of internal file descriptors */
10601077
nfds += extra_nfds; /* add the externally provided ones */
10611078

1079+
#ifdef ENABLE_WAKEUP
1080+
if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) {
1081+
++nfds;
1082+
}
1083+
#endif
1084+
10621085
if(nfds > NUM_POLLS_ON_STACK) {
10631086
/* 'nfds' is a 32 bit value and 'struct pollfd' is typically 8 bytes
10641087
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,
11171140
++nfds;
11181141
}
11191142

1143+
#ifdef ENABLE_WAKEUP
1144+
if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) {
1145+
ufds[nfds].fd = multi->wakeup_pair[0];
1146+
ufds[nfds].events = POLLIN;
1147+
++nfds;
1148+
}
1149+
#endif
1150+
11201151
if(nfds) {
11211152
int pollrc;
11221153
/* wait... */
@@ -1140,6 +1171,29 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
11401171

11411172
extra_fds[i].revents = mask;
11421173
}
1174+
1175+
#ifdef ENABLE_WAKEUP
1176+
if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) {
1177+
if(ufds[curlfds + extra_nfds].revents & POLLIN) {
1178+
char buf[64];
1179+
while(1) {
1180+
/* the reading socket is non-blocking, try to read
1181+
data from it until it receives an error (except EINTR).
1182+
In normal cases it will get EAGAIN or EWOULDBLOCK
1183+
when there is no more data, breaking the loop. */
1184+
if(sread(multi->wakeup_pair[0], buf, sizeof(buf)) < 0) {
1185+
#ifndef USE_WINSOCK
1186+
if(EINTR == SOCKERRNO)
1187+
continue;
1188+
#endif
1189+
break;
1190+
}
1191+
}
1192+
/* do not count the wakeup socket into the returned value */
1193+
retcode--;
1194+
}
1195+
}
1196+
#endif
11431197
}
11441198
}
11451199

@@ -1174,7 +1228,8 @@ CURLMcode curl_multi_wait(struct Curl_multi *multi,
11741228
int timeout_ms,
11751229
int *ret)
11761230
{
1177-
return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, FALSE);
1231+
return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, FALSE,
1232+
FALSE);
11781233
}
11791234

11801235
CURLMcode curl_multi_poll(struct Curl_multi *multi,
@@ -1183,7 +1238,55 @@ CURLMcode curl_multi_poll(struct Curl_multi *multi,
11831238
int timeout_ms,
11841239
int *ret)
11851240
{
1186-
return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, TRUE);
1241+
return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, TRUE,
1242+
TRUE);
1243+
}
1244+
1245+
CURLMcode curl_multi_wakeup(struct Curl_multi *multi)
1246+
{
1247+
/* this function is usually called from another thread,
1248+
it has to be careful only to access parts of the
1249+
Curl_multi struct that are constant */
1250+
1251+
/* GOOD_MULTI_HANDLE can be safely called */
1252+
if(!GOOD_MULTI_HANDLE(multi))
1253+
return CURLM_BAD_HANDLE;
1254+
1255+
#ifdef ENABLE_WAKEUP
1256+
/* the wakeup_pair variable is only written during init and cleanup,
1257+
making it safe to access from another thread after the init part
1258+
and before cleanup */
1259+
if(multi->wakeup_pair[1] != CURL_SOCKET_BAD) {
1260+
char buf[1];
1261+
buf[0] = 1;
1262+
while(1) {
1263+
/* swrite() is not thread-safe in general, because concurrent calls
1264+
can have their messages interleaved, but in this case the content
1265+
of the messages does not matter, which makes it ok to call.
1266+
1267+
The write socket is set to non-blocking, this way this function
1268+
cannot block, making it safe to call even from the same thread
1269+
that will call Curl_multi_wait(). If swrite() returns that it
1270+
would block, it's considered successful because it means that
1271+
previous calls to this function will wake up the poll(). */
1272+
if(swrite(multi->wakeup_pair[1], buf, sizeof(buf)) < 0) {
1273+
int err = SOCKERRNO;
1274+
int return_success;
1275+
#ifdef USE_WINSOCK
1276+
return_success = WSAEWOULDBLOCK == err;
1277+
#else
1278+
if(EINTR == err)
1279+
continue;
1280+
return_success = EWOULDBLOCK == err || EAGAIN == err;
1281+
#endif
1282+
if(!return_success)
1283+
return CURLM_WAKEUP_FAILURE;
1284+
}
1285+
return CURLM_OK;
1286+
}
1287+
}
1288+
#endif
1289+
return CURLM_WAKEUP_FAILURE;
11871290
}
11881291

11891292
/*
@@ -2309,6 +2412,11 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
23092412

23102413
Curl_hash_destroy(&multi->hostcache);
23112414
Curl_psl_destroy(&multi->psl);
2415+
2416+
#ifdef ENABLE_WAKEUP
2417+
sclose(multi->wakeup_pair[0]);
2418+
sclose(multi->wakeup_pair[1]);
2419+
#endif
23122420
free(multi);
23132421

23142422
return CURLM_OK;

lib/multihandle.h

+10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#include "conncache.h"
2626
#include "psl.h"
27+
#include "socketpair.h"
2728

2829
struct Curl_message {
2930
struct curl_llist_element list;
@@ -66,6 +67,10 @@ typedef enum {
6667

6768
#define CURLPIPE_ANY (CURLPIPE_MULTIPLEX)
6869

70+
#if defined(USE_SOCKETPAIR) && !defined(USE_BLOCKING_SOCKETS)
71+
#define ENABLE_WAKEUP
72+
#endif
73+
6974
/* This is the struct known as CURLM on the outside */
7075
struct Curl_multi {
7176
/* First a simple identifier to easier detect if a user mix up
@@ -134,6 +139,11 @@ struct Curl_multi {
134139
previous callback */
135140
bool in_callback; /* true while executing a callback */
136141
long max_concurrent_streams; /* max concurrent streams client to support */
142+
143+
#ifdef ENABLE_WAKEUP
144+
curl_socket_t wakeup_pair[2]; /* socketpair() used for wakeup
145+
0 is used for read, 1 is used for write */
146+
#endif
137147
};
138148

139149
#endif /* HEADER_CURL_MULTIHANDLE_H */

lib/strerror.c

+3
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,9 @@ curl_multi_strerror(CURLMcode error)
389389
case CURLM_RECURSIVE_API_CALL:
390390
return "API function called from within callback";
391391

392+
case CURLM_WAKEUP_FAILURE:
393+
return "Wakeup is unavailable or failed";
394+
392395
case CURLM_LAST:
393396
break;
394397
}

packages/OS400/curl.inc.in

+5-1
Original file line numberDiff line numberDiff line change
@@ -1804,7 +1804,11 @@
18041804
d c 6
18051805
d CURLM_ADDED_ALREADY...
18061806
d c 7
1807-
d CURLM_LAST c 8
1807+
d CURLM_RECURSIVE_API_CALL...
1808+
d c 8
1809+
d CURLM_WAKEUP_FAILURE...
1810+
d c 9
1811+
d CURLM_LAST c 10
18081812
*
18091813
d CURLMSG s 10i 0 based(######ptr######) Enum
18101814
d CURLMSG_NONE c 0

tests/data/Makefile.inc

+1-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ test1525 test1526 test1527 test1528 test1529 test1530 test1531 test1532 \
179179
test1533 test1534 test1535 test1536 test1537 test1538 \
180180
test1540 test1541 \
181181
test1550 test1551 test1552 test1553 test1554 test1555 test1556 test1557 \
182-
test1558 test1559 test1560 test1561 test1562 test1563 \
182+
test1558 test1559 test1560 test1561 test1562 test1563 test1564 test1565 \
183183
\
184184
test1590 test1591 test1592 test1593 test1594 test1595 test1596 \
185185
\

tests/data/test1135

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ CURL_EXTERN CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
9292
CURL_EXTERN CURLMcode curl_multi_fdset(CURLM *multi_handle,
9393
CURL_EXTERN CURLMcode curl_multi_wait(CURLM *multi_handle,
9494
CURL_EXTERN CURLMcode curl_multi_poll(CURLM *multi_handle,
95+
CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *multi_handle);
9596
CURL_EXTERN CURLMcode curl_multi_perform(CURLM *multi_handle,
9697
CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle);
9798
CURL_EXTERN CURLMsg *curl_multi_info_read(CURLM *multi_handle,

0 commit comments

Comments
 (0)