lib: add multi_wakeup_internal

For threaded resolving, added an additional socket/eventfd pair to the
multi handle for notifications from threads. The original "double use"
of the standard wakeup pair did lead to regressions for apps.

The API definition of curl_multi_poll/wait/wakeup is pretty tight
regarding what effects what and adding notifications on top of that
broke what apps perceived to be the contract.

Fixes #22272
Reported-by: Sergei Zimmerman
Closes #22274
This commit is contained in:
Stefan Eissing 2026-07-08 10:34:51 +02:00 committed by Daniel Stenberg
parent bd58857201
commit 009fd378e8
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
8 changed files with 136 additions and 12 deletions

View file

@ -437,7 +437,7 @@ static void async_thrdd_item_process(void *arg)
#endif /* HAVE_GETADDRINFO */
#ifdef ENABLE_WAKEUP
#ifdef ENABLE_INTERNAL_WAKEUP
static void async_thrdd_event(const struct curl_thrdq *tqueue,
Curl_thrdq_event ev,
void *user_data)
@ -446,7 +446,7 @@ static void async_thrdd_event(const struct curl_thrdq *tqueue,
(void)tqueue;
switch(ev) {
case CURL_THRDQ_EV_ITEM_DONE:
(void)curl_multi_wakeup(multi);
Curl_multi_wakeup_internal(multi);
break;
default:
break;
@ -693,7 +693,7 @@ CURLcode Curl_async_pollset(struct Curl_easy *data,
#endif
if(!async->done) {
#ifndef ENABLE_WAKEUP
#ifndef ENABLE_INTERNAL_WAKEUP
timediff_t stutter_ms, elapsed_ms;
elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start);
if(elapsed_ms < 3)
@ -736,7 +736,7 @@ CURLcode Curl_async_take_result(struct Curl_easy *data,
if(thrdd->rr.channel)
(void)Curl_ares_perform(thrdd->rr.channel, 0);
#endif
#ifndef ENABLE_WAKEUP
#ifndef ENABLE_INTERNAL_WAKEUP
Curl_async_thrdd_multi_process(data->multi);
#endif

View file

@ -268,6 +268,10 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size,
multi->wakeup_pair[0] = CURL_SOCKET_BAD;
multi->wakeup_pair[1] = CURL_SOCKET_BAD;
#endif
#ifdef ENABLE_INTERNAL_WAKEUP
multi->wakeup_internal[0] = CURL_SOCKET_BAD;
multi->wakeup_internal[1] = CURL_SOCKET_BAD;
#endif
if(Curl_mntfy_resize(multi) ||
Curl_uint32_bset_resize(&multi->process, xfer_table_size) ||
@ -315,6 +319,10 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size,
if(Curl_wakeup_init(multi->wakeup_pair, TRUE) < 0)
goto error;
#endif
#ifdef ENABLE_INTERNAL_WAKEUP
if(Curl_wakeup_init(multi->wakeup_internal, TRUE) < 0)
goto error;
#endif
if(Curl_probeipv6(multi))
goto error;
@ -360,6 +368,9 @@ error:
#ifdef ENABLE_WAKEUP
Curl_wakeup_destroy(multi->wakeup_pair);
#endif
#ifdef ENABLE_INTERNAL_WAKEUP
Curl_wakeup_destroy(multi->wakeup_internal);
#endif
curlx_free(multi);
return NULL;
@ -395,7 +406,7 @@ bool Curl_is_connecting(struct Curl_easy *data)
static CURLMcode multi_assess_wakeup(struct Curl_multi *multi)
{
#ifdef ENABLE_WAKEUP
#ifdef ENABLE_INTERNAL_WAKEUP
if(multi->socket_cb)
return Curl_multi_ev_assess_xfer(multi, multi->admin);
#else
@ -1148,14 +1159,14 @@ CURLMcode Curl_multi_pollset(struct Curl_easy *data,
CURLcode result = CURLE_OK;
Curl_pollset_reset(ps);
#ifdef ENABLE_WAKEUP
#ifdef ENABLE_INTERNAL_WAKEUP
/* The admin handle always listens on the wakeup socket when there
* are transfers alive. */
if(data->multi && (data == data->multi->admin) &&
data->multi->xfers_really_alive) {
CURL_TRC_M(data, "adding wakeup, %u xfers really alive",
data->multi->xfers_really_alive);
result = Curl_pollset_add_in(data, ps, data->multi->wakeup_pair[0]);
result = Curl_pollset_add_in(data, ps, data->multi->wakeup_internal[0]);
}
#endif
/* If the transfer has no connection, this is fine. Happens when
@ -1713,6 +1724,18 @@ CURLMcode curl_multi_wakeup(CURLM *m)
return mresult;
}
#ifdef ENABLE_INTERNAL_WAKEUP
void Curl_multi_wakeup_internal(struct Curl_multi *multi)
{
/* This is expected to be invocable from another thread which
* does NOT outlive the multi handle. Check for sanity. */
if(GOOD_MULTI_HANDLE(multi))
Curl_wakeup_signal(multi->wakeup_internal);
else
DEBUGASSERT(0);
}
#endif
/*
* multi_ischanged() is called
*
@ -2739,10 +2762,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
Curl_uint32_bset_remove(&multi->dirty, data->mid);
if(data == multi->admin) {
#ifdef ENABLE_WAKEUP
#ifdef ENABLE_INTERNAL_WAKEUP
/* Consume any pending wakeup signals before processing.
* This is necessary for event based processing. See #21547 */
(void)Curl_wakeup_consume(multi->wakeup_pair, TRUE);
(void)Curl_wakeup_consume(multi->wakeup_internal, TRUE);
#endif
#ifdef USE_RESOLV_THREADED
Curl_async_thrdd_multi_process(multi);
@ -3050,6 +3073,9 @@ CURLMcode curl_multi_cleanup(CURLM *m)
#ifdef ENABLE_WAKEUP
Curl_wakeup_destroy(multi->wakeup_pair);
#endif
#ifdef ENABLE_INTERNAL_WAKEUP
Curl_wakeup_destroy(multi->wakeup_internal);
#endif
multi_xfer_bufs_free(multi);
Curl_mntfy_cleanup(multi);

View file

@ -77,6 +77,11 @@ typedef enum {
#if !defined(CURL_DISABLE_SOCKETPAIR) && !defined(USE_WINSOCK)
#define ENABLE_WAKEUP
#endif
#if !defined(CURL_DISABLE_SOCKETPAIR) && \
defined(USE_RESOLV_THREADED) && \
!defined(USE_WINSOCK)
#define ENABLE_INTERNAL_WAKEUP
#endif
/* value for MAXIMUM CONCURRENT STREAMS upper limit */
#define INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1)
@ -172,7 +177,13 @@ struct Curl_multi {
#ifdef ENABLE_WAKEUP
curl_socket_t wakeup_pair[2]; /* eventfd()/pipe()/socketpair() used for
wakeup 0 is used for read, 1 is used
for write */
for write. Used by curl_multi_wakeup() */
#endif
#ifdef ENABLE_INTERNAL_WAKEUP
curl_socket_t wakeup_internal[2]; /* eventfd()/pipe()/socketpair() used for
wakeup 0 is used for read, 1 is used
for write. Used for internal wakeups,
e.g. threaded resolver. */
#endif
unsigned int max_concurrent_streams;
unsigned int maxconnects; /* if >0, a fixed limit of the maximum number of

View file

@ -164,4 +164,8 @@ void Curl_multi_clear_dirty(struct Curl_easy *data);
void Curl_multi_set_now(struct Curl_multi *multi);
#ifdef ENABLE_INTERNAL_WAKEUP
void Curl_multi_wakeup_internal(struct Curl_multi *multi);
#endif
#endif /* HEADER_CURL_MULTIIF_H */