Skip to content

Commit

Permalink
add curl_multi_unblock()
Browse files Browse the repository at this point in the history
This commit adds curl_multi_unblock() which was previously in the TODO
list. To enable the functionality a new setopt option
CURLMOPT_ENABLE_UNBLOCK has been also added.

On some platforms and with some configurations this feature might not be
available or can fail, in these cases a new error code
(CURLM_UNBLOCK_FAILURE) is returned from curl_multi_setopt() or from
curl_multi_unblock().

Fixes curl#4418
  • Loading branch information
ngg committed Nov 18, 2019
1 parent d1476aa commit a62e4da
Show file tree
Hide file tree
Showing 12 changed files with 185 additions and 10 deletions.
8 changes: 0 additions & 8 deletions docs/TODO
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
Expand Up @@ -54,6 +54,7 @@ man_MANS = \
curl_multi_socket_all.3 \
curl_multi_strerror.3 \
curl_multi_timeout.3 \
curl_multi_unblock.3 \
curl_multi_wait.3 \
curl_share_cleanup.3 \
curl_share_init.3 \
Expand Down
37 changes: 37 additions & 0 deletions docs/libcurl/curl_multi_unblock.3
@@ -0,0 +1,37 @@
.\" **************************************************************************
.\" * _ _ ____ _
.\" * 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_unblock 3 "17 Nov 2019" "libcurl 7.68.0" "libcurl Manual"
.SH NAME
curl_multi_unblock - unblocks a "hanging" curl_multi_wait/poll call
.SH SYNOPSIS
#include <curl/curl.h>

CURLMcode curl_multi_unblock(CURLM *multi_handle);
.ad
.SH DESCRIPTION
This function can be called from any thread and it unblocks (wakes up) a
"hanging" \fIcurl_multi_wait(3)\fP or \fIcurl_multi_poll(3)\fP call
that is currently (or will be) waiting for activity or a timeout.
.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
Expand Up @@ -291,6 +291,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_UNBLOCK_FAILURE (9)"
curl_multi_unblock() failure.
.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
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_UNBLOCK_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
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_UNBLOCK_FAILURE, /* curl_multi_unblock() 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_unblock()
*
* Desc: unblocks a "hanging" curl_multi_wait/poll call.
*
* Returns: CURLMcode type, general multi error code.
*/
CURL_EXTERN CURLMcode curl_multi_unblock(CURLM *multi_handle);

/*
* Name: curl_multi_perform()
*
Expand Down
111 changes: 111 additions & 0 deletions lib/multi.c
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 @@ -345,6 +346,9 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */
int chashsize) /* connection hash */
{
struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi));
#ifdef ENABLE_UNBLOCK
curl_socket_t sock_pair[2];
#endif

if(!multi)
return NULL;
Expand All @@ -367,6 +371,25 @@ 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_UNBLOCK
if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &sock_pair[0]) < 0) {
multi->unblock_read_socket = CURL_SOCKET_BAD;
multi->unblock_write_socket = CURL_SOCKET_BAD;
}
else if(curlx_nonblock(sock_pair[0], TRUE) < 0 ||
curlx_nonblock(sock_pair[1], TRUE) < 0) {
sclose(sock_pair[0]);
sclose(sock_pair[1]);
multi->unblock_read_socket = CURL_SOCKET_BAD;
multi->unblock_write_socket = CURL_SOCKET_BAD;
}
else {
multi->unblock_read_socket = sock_pair[0];
multi->unblock_write_socket = sock_pair[1];
}
#endif

return multi;

error:
Expand Down Expand Up @@ -1059,6 +1082,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_UNBLOCK
if(multi->unblock_read_socket != 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 +1146,14 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
++nfds;
}

#ifdef ENABLE_UNBLOCK
if(multi->unblock_read_socket != CURL_SOCKET_BAD) {
ufds[nfds].fd = multi->unblock_read_socket;
ufds[nfds].events = POLLIN;
++nfds;
}
#endif

if(nfds) {
int pollrc;
/* wait... */
Expand All @@ -1140,6 +1177,28 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,

extra_fds[i].revents = mask;
}

#ifdef ENABLE_UNBLOCK
if(multi->unblock_read_socket != CURL_SOCKET_BAD) {
if(ufds[curlfds + extra_nfds].revents & POLLIN) {
char buf[64];
while(1) {
/* the reading socket is non-blocking, but if EAGAIN or
EWOULDBLOCK is returned (which should not because
poll() returned that we can read) then we do not try
again, because most probably it will not be solved
until the next unblock call */
if(sread(multi->unblock_read_socket, buf, sizeof(buf)) < 0) {
#ifndef USE_WINSOCK
if(EINTR == SOCKERRNO)
continue;
#endif
}
break;
}
}
}
#endif
}
}

Expand Down Expand Up @@ -1186,6 +1245,53 @@ CURLMcode curl_multi_poll(struct Curl_multi *multi,
return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, TRUE);
}

CURLMcode curl_multi_unblock(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_UNBLOCK
/* unblock_write_socket is only written during init and cleanup,
making it safe to access from another thread after the init
part and before cleanup */
if(multi->unblock_write_socket != 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 unblock the poll(). */
if(swrite(multi->unblock_write_socket, 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_UNBLOCK_FAILURE;
}
return CURLM_OK;
}
}
#endif
return CURLM_UNBLOCK_FAILURE;
}

/*
* multi_ischanged() is called
*
Expand Down Expand Up @@ -2309,6 +2415,11 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi)

Curl_hash_destroy(&multi->hostcache);
Curl_psl_destroy(&multi->psl);

#ifdef ENABLE_UNBLOCK
sclose(multi->unblock_read_socket);
sclose(multi->unblock_write_socket);
#endif
free(multi);

return CURLM_OK;
Expand Down
12 changes: 12 additions & 0 deletions lib/multihandle.h
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_UNBLOCK
#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,13 @@ 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_UNBLOCK
curl_socket_t unblock_read_socket; /* reader half of the socketpair()
used for unblock */
curl_socket_t unblock_write_socket; /* writer half of the socketpair()
used for unblock */
#endif
};

#endif /* HEADER_CURL_MULTIHANDLE_H */
3 changes: 3 additions & 0 deletions lib/strerror.c
Expand Up @@ -388,6 +388,9 @@ curl_multi_strerror(CURLMcode error)
case CURLM_RECURSIVE_API_CALL:
return "API function called from within callback";

case CURLM_UNBLOCK_FAILURE:
return "curl_multi_unblock() is unavailable or failed";

case CURLM_LAST:
break;
}
Expand Down
6 changes: 5 additions & 1 deletion packages/OS400/curl.inc.in
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_UNBLOCK_FAILURE...
d c 9
d CURLM_LAST c 10
*
d CURLMSG s 10i 0 based(######ptr######) Enum
d CURLMSG_NONE c 0
Expand Down
1 change: 1 addition & 0 deletions tests/data/test1135
Expand Up @@ -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_unblock(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,
Expand Down
3 changes: 2 additions & 1 deletion tests/data/test1538
Expand Up @@ -138,7 +138,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: curl_multi_unblock() is unavailable or failed
m10: Unknown error
s0: No error
s1: Unknown share option
s2: Share currently in use
Expand Down

0 comments on commit a62e4da

Please sign in to comment.