async-thrdd: use thread queue for resolving
No binary (build failed)
—
(log yok)
| @@ -100,6 +100,14 @@ Pointer to pass to push callback. See CURLMOPT_PUSHDATA(3) | |
| Callback that approves or denies server pushes. See CURLMOPT_PUSHFUNCTION(3) | |
| + | ## CURLMOPT_QUICK_EXIT |
| + | |
| + | Enable a quicker cleanup of the multi handle. See CURLMOPT_QUICK_EXIT(3) |
| + | |
| + | ## CURLMOPT_RESOLVE_THREADS_MAX |
| + | |
| + | Max threads used for threaded DNS resolver. See CURLMOPT_RESOLVE_THREADS_MAX(3) |
| + | |
| ## CURLMOPT_SOCKETDATA | |
| Custom pointer passed to the socket callback. See CURLMOPT_SOCKETDATA(3) | |
| @@ -173,3 +173,20 @@ Make a blocking, graceful shutdown of all remaining connections when | |
| a multi handle is destroyed. This implicitly triggers for easy handles | |
| that are run via easy_perform. The value of the environment variable | |
| gives the shutdown timeout in milliseconds. | |
| + | |
| + | ## `CURL_DBG_RESOLV_MAX_THREADS` |
| + | |
| + | Overrides the maximum number of threads for resolver. |
| + | |
| + | ## `CURL_DBG_RESOLV_DELAY` |
| + | |
| + | Makes ever threaded resolve experience an initial delay in milliseconds. |
| + | |
| + | ## `CURL_DBG_RESOLV_FAIL_DELAY` |
| + | |
| + | With a threaded resolver, delay each lookup by the given milliseconds |
| + | and give a negative answer. |
| + | |
| + | ## `CURL_DBG_RESOLV_FAIL_IPV6` |
| + | |
| + | Make libcurl fail a resolve for IPv6 only. |
| @@ -0,0 +1,60 @@ | |
| + | --- |
| + | c: Copyright (C) Daniel Stenberg, <[email protected]>, et al. |
| + | SPDX-License-Identifier: curl |
| + | Title: CURLMOPT_QUICK_EXIT |
| + | Section: 3 |
| + | Source: libcurl |
| + | See-also: |
| + | - CURLOPT_QUICK_EXIT (3) |
| + | Protocol: |
| + | - All |
| + | Added-in: 8.20.0 |
| + | --- |
| + | |
| + | # NAME |
| + | |
| + | CURLOPT_QUICK_EXIT - allow libcurl to exit quickly |
| + | |
| + | # SYNOPSIS |
| + | |
| + | ~~~c |
| + | #include <curl/curl.h> |
| + | |
| + | CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_QUICK_EXIT, |
| + | long value); |
| + | ~~~ |
| + | |
| + | # DESCRIPTION |
| + | |
| + | Pass a long as a parameter, 1L meaning that when recovering from a timeout, |
| + | libcurl should skip lengthy cleanups that are intended to avoid all kinds of |
| + | leaks (threads etc.), as the caller program is about to call exit() anyway. |
| + | This allows for a swift termination after a DNS timeout for example, by |
| + | canceling and/or forgetting about a resolver thread, at the expense of a |
| + | possible (though short-lived) leak of associated resources. |
| + | |
| + | # DEFAULT |
| + | |
| + | 20. |
| + | |
| + | # %PROTOCOLS% |
| + | |
| + | # EXAMPLE |
| + | |
| + | ~~~c |
| + | int main(void) |
| + | { |
| + | CURLM *m = curl_multi_init(); |
| + | /* do not join threads when cleaning up this multi handle */ |
| + | curl_multi_setopt(m, CURLMOPT_QUICK_EXIT, 1L); |
| + | } |
| + | ~~~ |
| + | |
| + | # %AVAILABILITY% |
| + | |
| + | # RETURN VALUE |
| + | |
| + | curl_multi_setopt(3) returns a CURLMcode indicating success or error. |
| + | |
| + | CURLM_OK (0) means everything was OK, non-zero means an error occurred, see |
| + | libcurl-errors(3). |
| @@ -0,0 +1,75 @@ | |
| + | --- |
| + | c: Copyright (C) Daniel Stenberg, <[email protected]>, et al. |
| + | SPDX-License-Identifier: curl |
| + | Title: CURLMOPT_RESOLVE_THREADS_MAX |
| + | Section: 3 |
| + | Source: libcurl |
| + | See-also: |
| + | - CURLOPT_IPRESOLVE (3) |
| + | - CURLOPT_RESOLVE (3) |
| + | Protocol: |
| + | - All |
| + | Added-in: 8.20.0 |
| + | --- |
| + | |
| + | # NAME |
| + | |
| + | CURLMOPT_RESOLVE_THREADS_MAX - max threads for threaded DNS resolver |
| + | |
| + | # SYNOPSIS |
| + | |
| + | ~~~c |
| + | #include <curl/curl.h> |
| + | |
| + | CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_RESOLVE_THREADS_MAX, |
| + | long amount); |
| + | ~~~ |
| + | |
| + | # DESCRIPTION |
| + | |
| + | Pass a long for the **amount**. The set number is used as the maximum number |
| + | of threads to be used for the threaded DNS resolver. It has to be a |
| + | positive number in the range of 32 bits. |
| + | |
| + | When libcurl is built with a threaded resolver, which is the default on |
| + | many systems, it uses a thread pool to lookup addresses and other |
| + | properties of hostnames so other transfers are not blocked by this. |
| + | |
| + | Threads are started on demand to perform the resolving and shut down |
| + | again after a period of inactivity. When the maximum number of threads |
| + | is reached, outstanding resolves are held in a queue and served when |
| + | a thread becomes available. |
| + | |
| + | The default maximum is expected to work fine for many situations. Application |
| + | may override it using this option for the multi handle. |
| + | |
| + | Changing this value while there are resolves in progress is possible. |
| + | Increasing the value takes effect right away. Lowering the value does |
| + | not close down any resolves, but ends threads above the new maximum |
| + | once the resolving is done. |
| + | |
| + | # DEFAULT |
| + | |
| + | 20. |
| + | |
| + | # %PROTOCOLS% |
| + | |
| + | # EXAMPLE |
| + | |
| + | ~~~c |
| + | int main(void) |
| + | { |
| + | CURLM *m = curl_multi_init(); |
| + | /* never use more than 5 threads for resolving */ |
| + | curl_multi_setopt(m, CURLMOPT_RESOLVE_THREADS_MAX, 5L); |
| + | } |
| + | ~~~ |
| + | |
| + | # %AVAILABILITY% |
| + | |
| + | # RETURN VALUE |
| + | |
| + | curl_multi_setopt(3) returns a CURLMcode indicating success or error. |
| + | |
| + | CURLM_OK (0) means everything was OK, non-zero means an error occurred, see |
| + | libcurl-errors(3). |
| @@ -122,6 +122,8 @@ man_MANS = \ | |
| CURLMOPT_PIPELINING_SITE_BL.3 \ | |
| CURLMOPT_PUSHDATA.3 \ | |
| CURLMOPT_PUSHFUNCTION.3 \ | |
| + | CURLMOPT_QUICK_EXIT.3 \ |
| + | CURLMOPT_RESOLVE_THREADS_MAX.3 \ |
| CURLMOPT_SOCKETDATA.3 \ | |
| CURLMOPT_SOCKETFUNCTION.3 \ | |
| CURLMOPT_TIMERDATA.3 \ | |
| @@ -579,6 +579,8 @@ CURLMOPT_PIPELINING_SERVER_BL 7.30.0 | |
| CURLMOPT_PIPELINING_SITE_BL 7.30.0 | |
| CURLMOPT_PUSHDATA 7.44.0 | |
| CURLMOPT_PUSHFUNCTION 7.44.0 | |
| + | CURLMOPT_QUICK_EXIT 8.20.0 |
| + | CURLMOPT_RESOLVE_THREADS_MAX 8.20.0 |
| CURLMOPT_SOCKETDATA 7.15.4 | |
| CURLMOPT_SOCKETFUNCTION 7.15.4 | |
| CURLMOPT_TIMERDATA 7.16.0 | |
| @@ -403,6 +403,12 @@ typedef enum { | |
| /* This is the argument passed to the notify callback */ | |
| CURLOPT(CURLMOPT_NOTIFYDATA, CURLOPTTYPE_OBJECTPOINT, 19), | |
| + | /* maximum number of threads used with threaded DNS resolver */ |
| + | CURLOPT(CURLMOPT_RESOLVE_THREADS_MAX, CURLOPTTYPE_LONG, 20), |
| + | |
| + | /* set to 1L for not joining threads when multi is cleaned up */ |
| + | CURLOPT(CURLMOPT_QUICK_EXIT, CURLOPTTYPE_LONG, 21), |
| + | |
| CURLMOPT_LASTENTRY /* the last unused */ | |
| } CURLMoption; | |
| @@ -28,8 +28,6 @@ | |
| **********************************************************************/ | |
| #ifdef USE_RESOLV_THREADED | |
| - | #include "socketpair.h" |
| - | |
| #ifdef HAVE_NETINET_IN_H | |
| #include <netinet/in.h> | |
| #endif | |
| @@ -44,10 +42,6 @@ | |
| #include <inet.h> | |
| #endif | |
| - | #if defined(HAVE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) |
| - | #include <pthread.h> |
| - | #endif |
| - | |
| #ifdef HAVE_GETADDRINFO | |
| #define RESOLVER_ENOMEM EAI_MEMORY /* = WSA_NOT_ENOUGH_MEMORY on Windows */ | |
| #else | |
| @@ -64,7 +58,11 @@ | |
| #include "multiif.h" | |
| #include "curl_threads.h" | |
| #include "progress.h" | |
| + | #include "rand.h" |
| #include "select.h" | |
| + | #include "thrdqueue.h" |
| + | #include "curlx/strparse.h" |
| + | #include "curlx/wait.h" |
| #ifdef USE_ARES | |
| #include <ares.h> | |
| @@ -111,221 +109,115 @@ CURLcode Curl_async_get_impl(struct Curl_easy *data, | |
| return CURLE_OK; | |
| } | |
| - | /* Give up reference to add_ctx */ |
| - | static void addr_ctx_unlink(struct async_thrdd_addr_ctx **paddr_ctx, |
| - | struct Curl_easy *data) |
| - | { |
| - | struct async_thrdd_addr_ctx *addr_ctx = *paddr_ctx; |
| - | bool destroy; |
| - | |
| - | if(!addr_ctx) |
| - | return; |
| - | |
| - | Curl_mutex_acquire(&addr_ctx->mutx); |
| - | if(!data) /* called by resolving thread */ |
| - | addr_ctx->thrd_done = TRUE; |
| - | |
| - | DEBUGASSERT(addr_ctx->ref_count); |
| - | --addr_ctx->ref_count; |
| - | destroy = !addr_ctx->ref_count; |
| - | Curl_mutex_release(&addr_ctx->mutx); |
| - | |
| - | if(destroy) { |
| - | Curl_mutex_destroy(&addr_ctx->mutx); |
| - | curlx_free(addr_ctx->hostname); |
| - | if(addr_ctx->res) |
| - | Curl_freeaddrinfo(addr_ctx->res); |
| - | Curl_wakeup_destroy(addr_ctx->sock_pair); |
| - | curlx_free(addr_ctx); |
| - | } |
| - | *paddr_ctx = NULL; |
| - | } |
| - | |
| - | /* Initialize context for threaded resolver */ |
| - | static struct async_thrdd_addr_ctx * |
| - | addr_ctx_create(struct Curl_easy *data, |
| - | struct Curl_resolv_async *async, |
| - | const struct addrinfo *hints) |
| - | { |
| - | struct async_thrdd_addr_ctx *addr_ctx = curlx_calloc(1, sizeof(*addr_ctx)); |
| - | if(!addr_ctx) |
| - | return NULL; |
| - | |
| - | addr_ctx->thread_hnd = curl_thread_t_null; |
| - | addr_ctx->port = async->port; |
| - | addr_ctx->ref_count = 1; |
| - | |
| - | #ifdef HAVE_GETADDRINFO |
| - | DEBUGASSERT(hints); |
| - | addr_ctx->hints = *hints; |
| + | #ifdef CURLVERBOSE |
| + | #define CURL_ASYN_ITEM_DESC_LEN 64 |
| + | #define async_item_description(x) (x)->description |
| #else | |
| - | (void)hints; |
| + | #define async_item_description(x) NULL |
| #endif | |
| - | Curl_mutex_init(&addr_ctx->mutx); |
| - | |
| - | #ifndef CURL_DISABLE_SOCKETPAIR |
| - | /* create socket pair or pipe */ |
| - | if(Curl_wakeup_init(addr_ctx->sock_pair, FALSE) < 0) { |
| - | addr_ctx->sock_pair[0] = CURL_SOCKET_BAD; |
| - | addr_ctx->sock_pair[1] = CURL_SOCKET_BAD; |
| - | goto err_exit; |
| - | } |
| + | struct async_thrdd_item { |
| + | struct Curl_addrinfo *res; |
| + | #ifdef HAVE_GETADDRINFO |
| + | struct addrinfo hints; |
| #endif | |
| - | addr_ctx->sock_error = 0; |
| - | |
| - | /* Copying hostname string because original can be destroyed by parent |
| - | * thread during gethostbyname execution. |
| - | */ |
| - | addr_ctx->hostname = curlx_strdup(async->hostname); |
| - | if(!addr_ctx->hostname) |
| - | goto err_exit; |
| - | |
| - | return addr_ctx; |
| + | #ifdef CURLVERBOSE |
| + | char description[CURL_ASYN_ITEM_DESC_LEN]; |
| + | #endif |
| + | int sock_error; |
| + | uint32_t mid; |
| + | uint32_t async_id; |
| + | uint16_t port; |
| + | uint8_t ip_version; |
| + | uint8_t transport; |
| + | #ifdef DEBUGBUILD |
| + | uint32_t delay_ms; |
| + | uint32_t delay_fail_ms; |
| + | #endif |
| + | char hostname[1]; |
| + | }; |
| - | err_exit: |
| - | addr_ctx_unlink(&addr_ctx, data); |
| - | return NULL; |
| + | /* Give up reference to add_ctx */ |
| + | static void async_thrdd_item_destroy(struct async_thrdd_item *item) |
| + | { |
| + | if(item) { |
| + | if(item->res) |
| + | Curl_freeaddrinfo(item->res); |
| + | curlx_free(item); |
| + | } |
| } | |
| - | #ifdef HAVE_GETADDRINFO |
| - | |
| - | /* |
| - | * getaddrinfo_thread() resolves a name and then exits. |
| - | * |
| - | * For builds without ARES, but with USE_IPV6, create a resolver thread |
| - | * and wait on it. |
| - | */ |
| - | static CURL_THREAD_RETURN_T CURL_STDCALL getaddrinfo_thread(void *arg) |
| + | /* Initialize context for threaded resolver */ |
| + | static struct async_thrdd_item * |
| + | async_thrdd_item_create(struct Curl_easy *data, |
| + | const char *hostname, uint16_t port, |
| + | uint8_t ip_version, uint8_t transport, |
| + | uint32_t async_id) |
| { | |
| - | struct async_thrdd_addr_ctx *addr_ctx = arg; |
| - | curl_bit do_abort; |
| - | |
| - | Curl_mutex_acquire(&addr_ctx->mutx); |
| - | do_abort = addr_ctx->do_abort; |
| - | Curl_mutex_release(&addr_ctx->mutx); |
| - | |
| - | if(!do_abort) { |
| - | char service[12]; |
| - | int rc; |
| - | |
| - | curl_msnprintf(service, sizeof(service), "%d", addr_ctx->port); |
| + | size_t hostlen = strlen(hostname); |
| + | struct async_thrdd_item *item; |
| + | VERBOSE(const char *qtype = "A"); |
| - | rc = Curl_getaddrinfo_ex(addr_ctx->hostname, service, |
| - | &addr_ctx->hints, &addr_ctx->res); |
| + | item = curlx_calloc(1, sizeof(*item) + hostlen); |
| + | if(!item) |
| + | return NULL; |
| - | if(rc) { |
| - | addr_ctx->sock_error = SOCKERRNO ? SOCKERRNO : rc; |
| - | if(addr_ctx->sock_error == 0) |
| - | addr_ctx->sock_error = RESOLVER_ENOMEM; |
| - | } |
| - | else { |
| - | Curl_addrinfo_set_port(addr_ctx->res, addr_ctx->port); |
| - | } |
| + | if(hostlen) /* NUL byte of name already in struct size */ |
| + | memcpy(item->hostname, hostname, hostlen); |
| + | item->port = port; |
| + | item->ip_version = ip_version; |
| + | item->transport = transport; |
| + | item->mid = data->mid; |
| + | item->async_id = async_id; |
| - | #ifndef CURL_DISABLE_SOCKETPAIR |
| - | Curl_mutex_acquire(&addr_ctx->mutx); |
| - | do_abort = addr_ctx->do_abort; |
| - | Curl_mutex_release(&addr_ctx->mutx); |
| - | |
| - | if(!do_abort) { |
| - | /* Thread is done, notify transfer */ |
| - | int err = Curl_wakeup_signal(addr_ctx->sock_pair); |
| - | if(err) { |
| - | /* update sock_error to errno */ |
| - | addr_ctx->sock_error = err; |
| - | } |
| + | #ifdef HAVE_GETADDRINFO |
| + | { |
| + | int pf = PF_INET; |
| + | #ifdef CURLRES_IPV6 |
| + | if((ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { |
| + | /* The stack seems to be IPv6-enabled */ |
| + | if(ip_version == CURL_IPRESOLVE_V6) |
| + | pf = PF_INET6; |
| + | else |
| + | pf = PF_UNSPEC; |
| } | |
| + | #endif /* CURLRES_IPV6 */ |
| + | item->hints.ai_family = pf; |
| + | item->hints.ai_socktype = Curl_socktype_for_transport(transport); |
| + | #ifdef CURLVERBOSE |
| + | qtype = (pf == PF_INET6) ? "AAAA" : "A+AAAA"; |
| #endif | |
| } | |
| + | #endif /* HAVE_GETADDRINFO */ |
| - | addr_ctx_unlink(&addr_ctx, NULL); |
| - | return 0; |
| - | } |
| - | |
| - | #else /* HAVE_GETADDRINFO */ |
| + | #ifdef CURLVERBOSE |
| + | curl_msnprintf(item->description, sizeof(item->description), |
| + | "[%" FMT_OFF_T "/%d] %s %s:%u", |
| + | data->id, item->async_id, qtype, item->hostname, item->port); |
| + | #endif |
| - | /* |
| - | * gethostbyname_thread() resolves a name and then exits. |
| - | */ |
| - | static CURL_THREAD_RETURN_T CURL_STDCALL gethostbyname_thread(void *arg) |
| - | { |
| - | struct async_thrdd_addr_ctx *addr_ctx = arg; |
| - | bool do_abort; |
| - | |
| - | Curl_mutex_acquire(&addr_ctx->mutx); |
| - | do_abort = addr_ctx->do_abort; |
| - | Curl_mutex_release(&addr_ctx->mutx); |
| - | |
| - | if(!do_abort) { |
| - | addr_ctx->res = Curl_ipv4_resolve_r(addr_ctx->hostname, addr_ctx->port); |
| - | if(!addr_ctx->res) { |
| - | addr_ctx->sock_error = SOCKERRNO; |
| - | if(addr_ctx->sock_error == 0) |
| - | addr_ctx->sock_error = RESOLVER_ENOMEM; |
| + | #ifdef DEBUGBUILD |
| + | { |
| + | const char *p = getenv("CURL_DBG_RESOLV_DELAY"); |
| + | if(p) { |
| + | curl_off_t l; |
| + | if(!curlx_str_number(&p, &l, UINT32_MAX)) { |
| + | item->delay_ms = (uint32_t)l; |
| + | } |
| } | |
| - | |
| - | Curl_mutex_acquire(&addr_ctx->mutx); |
| - | do_abort = addr_ctx->do_abort; |
| - | Curl_mutex_release(&addr_ctx->mutx); |
| - | #ifndef CURL_DISABLE_SOCKETPAIR |
| - | if(!do_abort) { |
| - | int err = Curl_wakeup_signal(addr_ctx->sock_pair); |
| - | if(err) { |
| - | /* update sock_error to errno */ |
| - | addr_ctx->sock_error = err; |
| + | p = getenv("CURL_DBG_RESOLV_FAIL_DELAY"); |
| + | if(p) { |
| + | curl_off_t l; |
| + | if(!curlx_str_number(&p, &l, UINT32_MAX)) { |
| + | unsigned char c = 0; |
| + | Curl_rand_bytes(data, FALSE, &c, 1); |
| + | item->delay_fail_ms = (uint32_t)l + c; |
| } | |
| } | |
| - | #endif |
| - | } |
| - | |
| - | addr_ctx_unlink(&addr_ctx, NULL); |
| - | return 0; |
| - | } |
| - | |
| - | #endif /* HAVE_GETADDRINFO */ |
| - | |
| - | /* |
| - | * async_thrdd_destroy() cleans up async resolver data and thread handle. |
| - | */ |
| - | static void async_thrdd_destroy(struct Curl_easy *data, |
| - | struct Curl_resolv_async *async) |
| - | { |
| - | struct async_thrdd_ctx *thrdd = &async->thrdd; |
| - | struct async_thrdd_addr_ctx *addr = thrdd->addr; |
| - | |
| - | #ifdef USE_HTTPSRR_ARES |
| - | if(thrdd->rr.channel) { |
| - | ares_destroy(thrdd->rr.channel); |
| - | thrdd->rr.channel = NULL; |
| } | |
| - | Curl_httpsrr_cleanup(&thrdd->rr.hinfo); |
| - | #endif |
| - | |
| - | if(thrdd->addr && (thrdd->addr->thread_hnd != curl_thread_t_null)) { |
| - | curl_bit done; |
| - | |
| - | Curl_mutex_acquire(&addr->mutx); |
| - | #ifndef CURL_DISABLE_SOCKETPAIR |
| - | if(!addr->do_abort) |
| - | Curl_multi_will_close(data, addr->sock_pair[0]); |
| #endif | |
| - | addr->do_abort = TRUE; |
| - | done = addr->thrd_done; |
| - | Curl_mutex_release(&addr->mutx); |
| - | if(done) { |
| - | Curl_thread_join(&addr->thread_hnd); |
| - | CURL_TRC_DNS(data, "async_thrdd_destroy, thread joined"); |
| - | } |
| - | else { |
| - | /* thread is still running. Detach it. */ |
| - | Curl_thread_destroy(&addr->thread_hnd); |
| - | CURL_TRC_DNS(data, "async_thrdd_destroy, thread detached"); |
| - | } |
| - | } |
| - | /* release our reference to the shared context */ |
| - | addr_ctx_unlink(&thrdd->addr, data); |
| + | return item; |
| } | |
| #ifdef USE_HTTPSRR_ARES | |
| @@ -344,15 +236,17 @@ static void async_thrdd_rr_done(void *user_data, ares_status_t status, | |
| thrdd->rr.result = Curl_httpsrr_from_ares(data, dnsrec, &thrdd->rr.hinfo); | |
| } | |
| - | static CURLcode async_rr_start(struct Curl_easy *data, int port) |
| + | static CURLcode async_rr_start(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | struct async_thrdd_ctx *thrdd = &data->state.async->thrdd; |
| + | struct async_thrdd_ctx *thrdd = &async->thrdd; |
| int status; | |
| char *rrname = NULL; | |
| DEBUGASSERT(!thrdd->rr.channel); | |
| - | if(port != 443) { |
| - | rrname = curl_maprintf("_%d_.https.%s", port, data->conn->host.name); |
| + | if(async->port != 443) { |
| + | rrname = curl_maprintf("_%d_.https.%s", |
| + | async->port, data->conn->host.name); |
| if(!rrname) | |
| return CURLE_OUT_OF_MEMORY; | |
| } | |
| @@ -385,292 +279,305 @@ static CURLcode async_rr_start(struct Curl_easy *data, int port) | |
| } | |
| #endif | |
| - | /* |
| - | * async_thrdd_init() starts a new thread that performs the actual |
| - | * resolve. This function returns before the resolve is done. |
| - | * |
| - | * Returns FALSE in case of failure, otherwise TRUE. |
| - | */ |
| - | static bool async_thrdd_init(struct Curl_easy *data, |
| - | struct Curl_resolv_async *async, |
| - | const struct addrinfo *hints) |
| + | void Curl_async_thrdd_shutdown(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | struct async_thrdd_ctx *thrdd = &async->thrdd; |
| - | struct async_thrdd_addr_ctx *addr_ctx; |
| + | Curl_async_thrdd_destroy(data, async); |
| + | } |
| - | /* !checksrc! disable ERRNOVAR 1 */ |
| - | int err = ENOMEM; |
| + | static bool async_thrdd_match_item(void *qitem, void *match_data) |
| + | { |
| + | struct Curl_easy *data = match_data; |
| + | struct async_thrdd_item *item = qitem; |
| + | return item->mid == data->mid; |
| + | } |
| - | DEBUGASSERT(!thrdd->addr); |
| + | void Curl_async_thrdd_destroy(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| + | { |
| + | (void)data; |
| + | if(async->thrdd.queued && !async->thrdd.done && |
| + | data->multi && data->multi->resolv_thrdq) { |
| + | /* Remove any resolve items still queued */ |
| + | Curl_thrdq_clear(data->multi->resolv_thrdq, |
| + | async_thrdd_match_item, data); |
| + | } |
| #ifdef USE_HTTPSRR_ARES | |
| - | DEBUGASSERT(!thrdd->rr.channel); |
| + | if(async->thrdd.rr.channel) { |
| + | ares_destroy(async->thrdd.rr.channel); |
| + | async->thrdd.rr.channel = NULL; |
| + | } |
| + | Curl_httpsrr_cleanup(&async->thrdd.rr.hinfo); |
| #endif | |
| + | async_thrdd_item_destroy(async->thrdd.resolved); |
| + | async->thrdd.resolved = NULL; |
| + | } |
| + | |
| + | /* |
| + | * Waits for a resolve to finish. This function should be avoided since using |
| + | * this risk getting the multi interface to "hang". |
| + | */ |
| + | CURLcode Curl_async_await(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async, |
| + | struct Curl_dns_entry **pdns) |
| + | { |
| + | struct async_thrdd_ctx *thrdd = &async->thrdd; |
| + | timediff_t milli, ms; |
| - | addr_ctx = addr_ctx_create(data, async, hints); |
| - | if(!addr_ctx) |
| - | goto err_exit; |
| - | thrdd->addr = addr_ctx; |
| + | CURL_TRC_DNS(data, "[async] await %s", async->hostname); |
| + | while(thrdd->queued && !thrdd->done) { |
| + | Curl_async_thrdd_multi_process(data->multi); |
| + | if(thrdd->done) |
| + | break; |
| - | /* passing addr_ctx to the thread adds a reference */ |
| - | addr_ctx->ref_count = 2; |
| - | addr_ctx->start = *Curl_pgrs_now(data); |
| + | ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); |
| + | if(ms < 3) |
| + | milli = 0; |
| + | else if(ms <= 50) |
| + | milli = ms / 3; |
| + | else if(ms <= 250) |
| + | milli = 50; |
| + | else |
| + | milli = 200; |
| + | CURL_TRC_DNS(data, "[async] await, waiting %" FMT_TIMEDIFF_T "ms", |
| + | milli); |
| + | curlx_wait_ms(milli); |
| + | } |
| + | return Curl_async_take_result(data, async, pdns); |
| + | } |
| #ifdef HAVE_GETADDRINFO | |
| - | addr_ctx->thread_hnd = Curl_thread_create(getaddrinfo_thread, addr_ctx); |
| - | #else |
| - | addr_ctx->thread_hnd = Curl_thread_create(gethostbyname_thread, addr_ctx); |
| - | #endif |
| - | if(addr_ctx->thread_hnd == curl_thread_t_null) { |
| - | /* The thread never started */ |
| - | addr_ctx->ref_count = 1; |
| - | addr_ctx->thrd_done = TRUE; |
| - | err = errno; |
| - | goto err_exit; |
| - | } |
| + | /* Process the item, using Curl_getaddrinfo_ex() */ |
| + | static void async_thrdd_item_process(void *arg) |
| + | { |
| + | struct async_thrdd_item *item = arg; |
| + | char service[12]; |
| + | int rc; |
| - | #ifdef USE_HTTPSRR_ARES |
| - | if(async_rr_start(data, async->port)) |
| - | infof(data, "Failed HTTPS RR operation"); |
| + | #ifdef DEBUGBUILD |
| + | if(item->delay_ms) { |
| + | curlx_wait_ms(item->delay_ms); |
| + | } |
| + | if(item->delay_fail_ms) { |
| + | curlx_wait_ms(item->delay_fail_ms); |
| + | return; |
| + | } |
| #endif | |
| - | CURL_TRC_DNS(data, "resolve thread started for of %s:%d", |
| - | async->hostname, async->port); |
| - | return TRUE; |
| - | |
| - | err_exit: |
| - | CURL_TRC_DNS(data, "resolve thread failed init: %d", err); |
| - | async_thrdd_destroy(data, async); |
| - | errno = err; |
| - | return FALSE; |
| + | curl_msnprintf(service, sizeof(service), "%d", item->port); |
| + | |
| + | rc = Curl_getaddrinfo_ex(item->hostname, service, |
| + | &item->hints, &item->res); |
| + | if(rc) { |
| + | item->sock_error = SOCKERRNO ? SOCKERRNO : rc; |
| + | if(item->sock_error == 0) |
| + | item->sock_error = RESOLVER_ENOMEM; |
| + | } |
| + | else { |
| + | Curl_addrinfo_set_port(item->res, item->port); |
| + | } |
| } | |
| - | static void async_thrdd_shutdown(struct Curl_easy *data, |
| - | struct Curl_resolv_async *async) |
| - | { |
| - | struct async_thrdd_ctx *thrdd = &async->thrdd; |
| - | struct async_thrdd_addr_ctx *addr_ctx = thrdd->addr; |
| - | curl_bit done; |
| + | #else /* HAVE_GETADDRINFO */ |
| - | if(!addr_ctx) |
| - | return; |
| - | if(addr_ctx->thread_hnd == curl_thread_t_null) |
| - | return; |
| + | /* Process the item, using Curl_ipv4_resolve_r() */ |
| + | static void async_thrdd_item_process(void *item) |
| + | { |
| + | struct async_thrdd_item *item = arg; |
| - | Curl_mutex_acquire(&addr_ctx->mutx); |
| - | #ifndef CURL_DISABLE_SOCKETPAIR |
| - | if(!addr_ctx->do_abort) |
| - | Curl_multi_will_close(data, addr_ctx->sock_pair[0]); |
| + | #ifdef DEBUGBUILD |
| + | if(item->delay_ms) { |
| + | curlx_wait_ms(item->delay_ms); |
| + | } |
| + | if(item->delay_fail_ms) { |
| + | curlx_wait_ms(item->delay_fail_ms); |
| + | return; |
| + | } |
| #endif | |
| - | addr_ctx->do_abort = TRUE; |
| - | done = addr_ctx->thrd_done; |
| - | Curl_mutex_release(&addr_ctx->mutx); |
| - | |
| - | /* Wait for the thread to terminate if it is already marked done. If it is |
| - | not done yet we cannot do anything here. We had tried pthread_cancel but |
| - | it caused hanging and resource leaks (#18532). */ |
| - | if(done && (addr_ctx->thread_hnd != curl_thread_t_null)) { |
| - | Curl_thread_join(&addr_ctx->thread_hnd); |
| - | CURL_TRC_DNS(data, "async_thrdd_shutdown, thread joined"); |
| + | item->res = Curl_ipv4_resolve_r(item->hostname, item->port); |
| + | if(!item->res) { |
| + | item->sock_error = SOCKERRNO; |
| + | if(item->sock_error == 0) |
| + | item->sock_error = RESOLVER_ENOMEM; |
| } | |
| } | |
| - | /* |
| - | * 'entry' may be NULL and then no data is returned |
| - | */ |
| - | static CURLcode asyn_thrdd_await(struct Curl_easy *data, |
| - | struct Curl_resolv_async *async, |
| - | struct Curl_dns_entry **entry) |
| + | #endif /* HAVE_GETADDRINFO */ |
| + | |
| + | #ifdef ENABLE_WAKEUP |
| + | static void async_thrdd_event(const struct curl_thrdq *tqueue, |
| + | Curl_thrdq_event ev, |
| + | void *user_data) |
| { | |
| - | struct async_thrdd_addr_ctx *addr_ctx = async->thrdd.addr; |
| - | CURLcode result = CURLE_OK; |
| + | struct Curl_multi *multi = user_data; |
| + | (void)tqueue; |
| + | switch(ev) { |
| + | case CURL_THRDQ_EV_ITEM_DONE: |
| + | (void)curl_multi_wakeup(multi); |
| + | break; |
| + | default: |
| + | break; |
| + | } |
| + | } |
| + | #else |
| + | #define async_thrdd_event NULL |
| + | #endif |
| - | if(addr_ctx && (addr_ctx->thread_hnd != curl_thread_t_null)) { |
| - | /* not interested in result? cancel, if still running... */ |
| - | if(!entry) |
| - | async_thrdd_shutdown(data, async); |
| + | static void async_thrdd_item_free(void *item) |
| + | { |
| + | async_thrdd_item_destroy(item); |
| + | } |
| - | if(addr_ctx->thread_hnd != curl_thread_t_null) { |
| - | CURL_TRC_DNS(data, "resolve, wait for thread to finish"); |
| - | if(!Curl_thread_join(&addr_ctx->thread_hnd)) { |
| - | DEBUGASSERT(0); |
| + | /* Create a thread queue for processing resolv items */ |
| + | CURLcode Curl_async_thrdd_multi_init(struct Curl_multi *multi, |
| + | uint32_t min_threads, |
| + | uint32_t max_threads, |
| + | uint32_t idle_time_ms) |
| + | { |
| + | CURLcode result; |
| + | DEBUGASSERT(!multi->resolv_thrdq); |
| + | result = Curl_thrdq_create(&multi->resolv_thrdq, "async", 0, |
| + | min_threads, max_threads, idle_time_ms, |
| + | async_thrdd_item_free, |
| + | async_thrdd_item_process, |
| + | async_thrdd_event, |
| + | multi); |
| + | #ifdef DEBUGBUILD |
| + | if(!result) { |
| + | const char *p = getenv("CURL_DBG_RESOLV_MAX_THREADS"); |
| + | if(p) { |
| + | curl_off_t l; |
| + | if(!curlx_str_number(&p, &l, UINT32_MAX)) { |
| + | result = Curl_async_thrdd_multi_set_props( |
| + | multi, min_threads, (uint32_t)l, idle_time_ms); |
| } | |
| } | |
| - | |
| - | if(entry) { |
| - | result = Curl_async_take_result(data, async, entry); |
| - | if(result == CURLE_AGAIN) |
| - | result = CURLE_OK; |
| - | } |
| } | |
| - | |
| + | #endif |
| return result; | |
| } | |
| - | /* |
| - | * Until we gain a way to signal the resolver threads to stop early, we must |
| - | * wait for them and ignore their results. |
| - | */ |
| - | void Curl_async_thrdd_shutdown(struct Curl_easy *data, |
| - | struct Curl_resolv_async *async) |
| + | /* Tear down the thread queue, joining active threads or detaching them */ |
| + | void Curl_async_thrdd_multi_destroy(struct Curl_multi *multi, bool join) |
| { | |
| - | async_thrdd_shutdown(data, async); |
| + | if(multi->resolv_thrdq) { |
| + | Curl_thrdq_destroy(multi->resolv_thrdq, join); |
| + | multi->resolv_thrdq = NULL; |
| + | } |
| } | |
| - | void Curl_async_thrdd_destroy(struct Curl_easy *data, |
| - | struct Curl_resolv_async *async) |
| + | /* Process the receiving end of the thread queue, dispatching |
| + | * processed items to their transfer when it can still be found |
| + | * and has an `async` state present. Otherwise, destroy the item. */ |
| + | void Curl_async_thrdd_multi_process(struct Curl_multi *multi) |
| { | |
| - | if(!data->set.quick_exit) { |
| - | (void)asyn_thrdd_await(data, async, NULL); |
| + | struct Curl_easy *data; |
| + | void *qitem; |
| + | |
| + | while(!Curl_thrdq_recv(multi->resolv_thrdq, &qitem)) { |
| + | /* dispatch resolve result */ |
| + | struct async_thrdd_item *item = qitem; |
| + | |
| + | CURL_TRC_DNS(multi->admin, "[async] got %s'%s'", |
| + | item->res ? "" : "negative for ", item->description); |
| + | |
| + | data = Curl_multi_get_easy(multi, item->mid); |
| + | /* there is a chance that the original resolve was discarded and |
| + | * either no new, or a new resolve with a different id is ongoing. */ |
| + | if(data && data->conn && data->state.async && |
| + | (data->state.async->id == item->async_id)) { |
| + | struct Curl_resolv_async *async = data->state.async; |
| + | |
| + | async->thrdd.resolved = item; |
| + | async->thrdd.done = TRUE; |
| + | item = NULL; |
| + | Curl_multi_mark_dirty(data); |
| + | } |
| + | async_thrdd_item_free(item); |
| } | |
| - | async_thrdd_destroy(data, async); |
| + | #ifdef CURLVERBOSE |
| + | Curl_thrdq_trace(multi->resolv_thrdq, multi->admin, &Curl_trc_feat_dns); |
| + | #endif |
| } | |
| - | /* |
| - | * Curl_async_await() |
| - | * |
| - | * Waits for a resolve to finish. This function should be avoided since using |
| - | * this risk getting the multi interface to "hang". |
| - | * |
| - | * If 'entry' is non-NULL, make it point to the resolved dns entry |
| - | * |
| - | * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, |
| - | * CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors. |
| - | * |
| - | * This is the version for resolves-in-a-thread. |
| - | */ |
| - | CURLcode Curl_async_await(struct Curl_easy *data, |
| - | struct Curl_resolv_async *async, |
| - | struct Curl_dns_entry **entry) |
| + | CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi, |
| + | uint32_t min_threads, |
| + | uint32_t max_threads, |
| + | uint32_t idle_time_ms) |
| { | |
| - | return asyn_thrdd_await(data, async, entry); |
| + | return Curl_thrdq_set_props(multi->resolv_thrdq, 0, |
| + | min_threads, max_threads, idle_time_ms); |
| } | |
| - | /* |
| - | * Curl_async_take_result() is called repeatedly to check if a previous |
| - | * name resolve request has completed. It should also make sure to time-out if |
| - | * the operation seems to take too long. |
| - | */ |
| - | CURLcode Curl_async_take_result(struct Curl_easy *data, |
| - | struct Curl_resolv_async *async, |
| - | struct Curl_dns_entry **pdns) |
| + | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async) |
| { | |
| - | struct async_thrdd_ctx *thrdd = &async->thrdd; |
| - | curl_bit done = FALSE; |
| - | |
| - | DEBUGASSERT(pdns); |
| - | *pdns = NULL; |
| - | |
| - | #ifdef USE_HTTPSRR_ARES |
| - | /* best effort, ignore errors */ |
| - | if(thrdd->rr.channel) |
| - | (void)Curl_ares_perform(thrdd->rr.channel, 0); |
| - | #endif |
| + | struct async_thrdd_item *item; |
| + | CURLcode result; |
| - | DEBUGASSERT(thrdd->addr); |
| - | if(!thrdd->addr) |
| + | if(async->thrdd.queued || async->thrdd.done || async->thrdd.resolved) |
| return CURLE_FAILED_INIT; | |
| - | Curl_mutex_acquire(&thrdd->addr->mutx); |
| - | done = thrdd->addr->thrd_done; |
| - | Curl_mutex_release(&thrdd->addr->mutx); |
| - | |
| - | if(done) { |
| - | CURLcode result = CURLE_OK; |
| - | |
| - | Curl_expire_done(data, EXPIRE_ASYNC_NAME); |
| + | item = async_thrdd_item_create(data, async->hostname, async->port, |
| + | async->ip_version, async->transport, |
| + | async->id); |
| + | if(!item) { |
| + | result = CURLE_OUT_OF_MEMORY; |
| + | goto out; |
| + | } |
| - | if(thrdd->addr->res) { |
| - | struct Curl_dns_entry *dns = |
| - | Curl_dnscache_mk_entry(data, &thrdd->addr->res, |
| - | async->hostname, async->port, |
| - | async->ip_version); |
| - | if(!dns) |
| - | result = CURLE_OUT_OF_MEMORY; |
| + | CURL_TRC_DNS(data, "[async] queueing %s", item->description); |
| + | /* queue takes ownership of `item` on success */ |
| + | result = Curl_thrdq_send(data->multi->resolv_thrdq, item, |
| + | async_item_description(item), async->timeout_ms); |
| + | if(!result) |
| + | async->thrdd.queued = TRUE; |
| + | else |
| + | async_thrdd_item_destroy(item); |
| + | #ifdef CURLVERBOSE |
| + | Curl_thrdq_trace(data->multi->resolv_thrdq, data, &Curl_trc_feat_dns); |
| + | #endif |
| #ifdef USE_HTTPSRR_ARES | |
| - | if(thrdd->rr.channel) { |
| - | result = thrdd->rr.result; |
| - | if(!result) { |
| - | struct Curl_https_rrinfo *lhrr; |
| - | lhrr = Curl_httpsrr_dup_move(&thrdd->rr.hinfo); |
| - | if(!lhrr) |
| - | result = CURLE_OUT_OF_MEMORY; |
| - | else |
| - | dns->hinfo = lhrr; |
| - | } |
| - | } |
| + | DEBUGASSERT(!async->thrdd.rr.channel); |
| + | if(async_rr_start(data, async)) |
| + | infof(data, "Failed HTTPS RR operation"); |
| #endif | |
| - | if(!result && dns) { |
| - | result = Curl_dnscache_add(data, dns); |
| - | *pdns = dns; |
| - | } |
| - | } |
| - | if(!result && !*pdns) |
| - | result = Curl_resolver_error(data, NULL); |
| - | CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound", |
| - | result, *pdns ? "" : "not "); |
| - | async_thrdd_shutdown(data, async); |
| - | return result; |
| - | } |
| - | else { |
| - | /* poll for name lookup done with exponential backoff up to 250ms */ |
| - | /* should be fine even if this converts to 32-bit */ |
| - | timediff_t elapsed = curlx_ptimediff_ms(Curl_pgrs_now(data), |
| - | &data->progress.t_startsingle); |
| - | if(elapsed < 0) |
| - | elapsed = 0; |
| - | |
| - | if(thrdd->addr->poll_interval == 0) |
| - | /* Start at 1ms poll interval */ |
| - | thrdd->addr->poll_interval = 1; |
| - | else if(elapsed >= thrdd->addr->interval_end) |
| - | /* Back-off exponentially if last interval expired */ |
| - | thrdd->addr->poll_interval *= 2; |
| - | |
| - | if(thrdd->addr->poll_interval > 250) |
| - | thrdd->addr->poll_interval = 250; |
| - | |
| - | thrdd->addr->interval_end = elapsed + thrdd->addr->poll_interval; |
| - | Curl_expire(data, thrdd->addr->poll_interval, EXPIRE_ASYNC_NAME); |
| - | return CURLE_AGAIN; |
| - | } |
| + | out: |
| + | if(result) |
| + | CURL_TRC_DNS(data, "[async] error queueing %s:%d -> %d", |
| + | async->hostname, async->port, result); |
| + | return result; |
| } | |
| CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps) | |
| { | |
| - | struct async_thrdd_ctx *thrdd = &data->state.async->thrdd; |
| - | CURLcode result = CURLE_OK; |
| - | curl_bit thrd_done; |
| - | |
| - | #if !defined(USE_HTTPSRR_ARES) && defined(CURL_DISABLE_SOCKETPAIR) |
| - | (void)ps; |
| - | #endif |
| + | struct Curl_resolv_async *async = data->state.async; |
| + | struct async_thrdd_ctx *thrdd = async ? &async->thrdd : NULL; |
| + | if(!thrdd) |
| + | return CURLE_OK; |
| #ifdef USE_HTTPSRR_ARES | |
| if(thrdd->rr.channel) { | |
| - | result = Curl_ares_pollset(data, thrdd->rr.channel, ps); |
| + | CURLcode result = Curl_ares_pollset(data, thrdd->rr.channel, ps); |
| if(result) | |
| return result; | |
| } | |
| + | #else |
| + | (void)ps; |
| #endif | |
| - | if(!thrdd->addr) |
| - | return result; |
| - | Curl_mutex_acquire(&thrdd->addr->mutx); |
| - | thrd_done = thrdd->addr->thrd_done; |
| - | Curl_mutex_release(&thrdd->addr->mutx); |
| - | |
| - | if(!thrd_done) { |
| - | #ifndef CURL_DISABLE_SOCKETPAIR |
| - | /* return read fd to client for polling the DNS resolution status */ |
| - | result = Curl_pollset_add_in(data, ps, thrdd->addr->sock_pair[0]); |
| + | if(!thrdd->done) { |
| + | #ifdef ENABLE_WAKEUP |
| + | /* The multi "wakeup" socket pair triggers result processing, |
| + | * no need for an extra timer. */ |
| + | (void)data; |
| #else | |
| timediff_t milli; | |
| - | timediff_t ms = |
| - | curlx_ptimediff_ms(Curl_pgrs_now(data), &thrdd->addr->start); |
| + | timediff_t ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &async->start); |
| if(ms < 3) | |
| - | milli = 0; |
| + | milli = 1; |
| else if(ms <= 50) | |
| milli = ms / 3; | |
| else if(ms <= 250) | |
| @@ -680,64 +587,83 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps) | |
| Curl_expire(data, milli, EXPIRE_ASYNC_NAME); | |
| #endif | |
| } | |
| - | return result; |
| + | return CURLE_OK; |
| } | |
| - | #ifndef HAVE_GETADDRINFO |
| /* | |
| - | * Curl_async_getaddrinfo() - for platforms without getaddrinfo |
| + | * Curl_async_take_result() is called repeatedly to check if a previous |
| + | * name resolve request has completed. It should also make sure to time-out if |
| + | * the operation seems to take too long. |
| */ | |
| - | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, |
| - | struct Curl_resolv_async *async) |
| + | CURLcode Curl_async_take_result(struct Curl_easy *data, |
| + | struct Curl_resolv_async *async, |
| + | struct Curl_dns_entry **pdns) |
| { | |
| - | (void)ip_version; |
| + | struct async_thrdd_ctx *thrdd = &async->thrdd; |
| + | CURLcode result = CURLE_OK; |
| - | /* fire up a new resolver thread! */ |
| - | if(async_thrdd_init(data, async, NULL)) { |
| - | return CURLE_OK; |
| + | DEBUGASSERT(pdns); |
| + | *pdns = NULL; |
| + | if(!thrdd->queued) { |
| + | DEBUGASSERT(0); |
| + | return CURLE_FAILED_INIT; |
| } | |
| - | failf(data, "getaddrinfo() thread failed"); |
| - | return CURLE_FAILED_INIT; |
| - | } |
| - | |
| - | #else /* !HAVE_GETADDRINFO */ |
| - | |
| - | /* |
| - | * Curl_async_getaddrinfo() - for getaddrinfo |
| - | */ |
| - | CURLcode Curl_async_getaddrinfo(struct Curl_easy *data, |
| - | struct Curl_resolv_async *async) |
| - | { |
| - | struct addrinfo hints; |
| - | int pf = PF_INET; |
| + | #ifdef USE_HTTPSRR_ARES |
| + | /* best effort, ignore errors */ |
| + | if(thrdd->rr.channel) |
| + | (void)Curl_ares_perform(thrdd->rr.channel, 0); |
| + | #endif |
| - | CURL_TRC_DNS(data, "init threaded resolve of %s:%d", |
| - | async->hostname, async->port); |
| - | #ifdef CURLRES_IPV6 |
| - | if((async->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { |
| - | /* The stack seems to be IPv6-enabled */ |
| - | if(async->ip_version == CURL_IPRESOLVE_V6) |
| - | pf = PF_INET6; |
| - | else |
| - | pf = PF_UNSPEC; |
| + | if(!thrdd->done) { |
| + | CURL_TRC_DNS(data, "[async] take %s:%d -> EAGAIN", |
| + | async->hostname, async->port); |
| + | return CURLE_AGAIN; |
| } | |
| - | #endif /* CURLRES_IPV6 */ |
| - | memset(&hints, 0, sizeof(hints)); |
| - | hints.ai_family = pf; |
| - | hints.ai_socktype = |
| - | (Curl_conn_get_transport(data, data->conn) == TRNSPRT_TCP) ? |
| - | SOCK_STREAM : SOCK_DGRAM; |
| + | Curl_expire_done(data, EXPIRE_ASYNC_NAME); |
| + | if(thrdd->resolved && thrdd->resolved->res) { |
| + | struct Curl_dns_entry *dns = |
| + | Curl_dnscache_mk_entry(data, &thrdd->resolved->res, |
| + | async->hostname, async->port, async->ip_version); |
| + | if(!dns) |
| + | result = CURLE_OUT_OF_MEMORY; |
| - | /* fire up a new resolver thread! */ |
| - | if(async_thrdd_init(data, async, &hints)) |
| - | return CURLE_OK; |
| + | #ifdef USE_HTTPSRR_ARES |
| + | if(!result && thrdd->rr.channel) { |
| + | result = thrdd->rr.result; |
| + | if(!result) { |
| + | struct Curl_https_rrinfo *lhrr; |
| + | lhrr = Curl_httpsrr_dup_move(&thrdd->rr.hinfo); |
| + | if(!lhrr) |
| + | result = CURLE_OUT_OF_MEMORY; |
| + | else |
| + | dns->hinfo = lhrr; |
| + | } |
| + | } |
| + | #endif |
| + | if(!result && dns) { |
| + | CURL_TRC_DNS(data, "[async] resolved: %s", |
| + | thrdd->resolved->description); |
| + | *pdns = dns; |
| + | dns = NULL; |
| + | } |
| + | Curl_dns_entry_unlink(data, &dns); |
| + | } |
| - | failf(data, "getaddrinfo() thread failed to start"); |
| - | return CURLE_FAILED_INIT; |
| + | #ifdef CURLVERBOSE |
| + | Curl_thrdq_trace(data->multi->resolv_thrdq, data, &Curl_trc_feat_dns); |
| + | #endif |
| + | if(!result && !*pdns) |
| + | result = Curl_resolver_error(data, NULL); |
| + | Curl_async_thrdd_shutdown(data, async); |
| + | if(result && |
| + | (result != CURLE_COULDNT_RESOLVE_HOST) && |
| + | (result != CURLE_COULDNT_RESOLVE_PROXY)) { |
| + | CURL_TRC_DNS(data, "[async] %s:%d: error %d", |
| + | async->hostname, async->port, result); |
| + | } |
| + | return result; |
| } | |
| - | #endif /* !HAVE_GETADDRINFO */ |
| - | |
| #endif /* USE_RESOLV_THREADED */ | |
| @@ -32,6 +32,7 @@ | |
| struct Curl_easy; | |
| struct Curl_dns_entry; | |
| struct Curl_resolv_async; | |
| + | struct Curl_multi; |
| #ifdef CURLRES_ASYNCH | |
| @@ -169,39 +170,12 @@ CURLcode Curl_async_ares_set_dns_local_ip6(struct Curl_easy *data); | |
| #endif /* USE_RESOLV_ARES */ | |
| #ifdef USE_RESOLV_THREADED | |
| - | /* async resolving implementation using POSIX threads */ |
| - | #include "curl_threads.h" |
| - | |
| - | /* Context for threaded address resolver */ |
| - | struct async_thrdd_addr_ctx { |
| - | curl_thread_t thread_hnd; |
| - | char *hostname; /* hostname to resolve, Curl_async.hostname |
| - | duplicate */ |
| - | curl_mutex_t mutx; |
| - | #ifndef CURL_DISABLE_SOCKETPAIR |
| - | curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */ |
| - | #endif |
| - | struct Curl_addrinfo *res; |
| - | #ifdef HAVE_GETADDRINFO |
| - | struct addrinfo hints; |
| - | #endif |
| - | struct curltime start; |
| - | timediff_t interval_end; |
| - | unsigned int poll_interval; |
| - | int port; |
| - | int sock_error; |
| - | int ref_count; |
| - | BIT(thrd_done); |
| - | BIT(do_abort); |
| - | }; |
| + | |
| + | struct async_thrdd_item; |
| /* Context for threaded resolver */ | |
| struct async_thrdd_ctx { | |
| - | /* `addr` is a pointer since this memory is shared with a started |
| - | * thread. Since threads cannot be killed, we use reference counting |
| - | * so that we can "release" our pointer to this memory while the |
| - | * thread is still running. */ |
| - | struct async_thrdd_addr_ctx *addr; |
| + | struct async_thrdd_item *resolved; |
| #if defined(USE_HTTPSRR) && defined(USE_ARES) | |
| struct { | |
| ares_channel channel; | |
| @@ -210,6 +184,8 @@ struct async_thrdd_ctx { | |
| BIT(done); | |
| } rr; | |
| #endif | |
| + | BIT(queued); |
| + | BIT(done); |
| }; | |
| void Curl_async_thrdd_shutdown(struct Curl_easy *data, | |
| @@ -217,6 +193,18 @@ void Curl_async_thrdd_shutdown(struct Curl_easy *data, | |
| void Curl_async_thrdd_destroy(struct Curl_easy *data, | |
| struct Curl_resolv_async *async); | |
| + | CURLcode Curl_async_thrdd_multi_init(struct Curl_multi *multi, |
| + | uint32_t min_threads, |
| + | uint32_t max_threads, |
| + | uint32_t idle_time_ms); |
| + | void Curl_async_thrdd_multi_destroy(struct Curl_multi *multi, bool join); |
| + | void Curl_async_thrdd_multi_process(struct Curl_multi *multi); |
| + | |
| + | CURLcode Curl_async_thrdd_multi_set_props(struct Curl_multi *multi, |
| + | uint32_t min_threads, |
| + | uint32_t max_threads, |
| + | uint32_t idle_time_ms); |
| + | |
| #endif /* USE_RESOLV_THREADED */ | |
| #ifndef CURL_DISABLE_DOH | |
| @@ -248,10 +236,15 @@ struct Curl_resolv_async { | |
| #ifndef CURL_DISABLE_DOH | |
| struct doh_probes *doh; /* DoH specific data for this request */ | |
| #endif | |
| + | struct curltime start; |
| + | timediff_t interval_end; |
| + | timediff_t timeout_ms; |
| + | uint32_t poll_interval; |
| uint32_t id; /* unique id per easy handle of the resolve operation */ | |
| - | /* what is being resolved */ |
| + | /* what is being resolved */ |
| uint16_t port; | |
| uint8_t ip_version; | |
| + | uint8_t transport; |
| char hostname[1]; | |
| }; | |
| @@ -530,7 +530,8 @@ CURLcode Curl_parse_interface(const char *input, | |
| #ifndef CURL_DISABLE_BINDLOCAL | |
| static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, | |
| - | curl_socket_t sockfd, int af, unsigned int scope) |
| + | curl_socket_t sockfd, int af, unsigned int scope, |
| + | uint8_t transport) |
| { | |
| struct Curl_sockaddr_storage sa; | |
| struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ | |
| @@ -648,7 +649,7 @@ static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn, | |
| ip_version = CURL_IPRESOLVE_V6; | |
| #endif | |
| - | (void)Curl_resolv_blocking(data, host, 80, ip_version, &h); |
| + | (void)Curl_resolv_blocking(data, host, 80, ip_version, transport, &h); |
| if(h) { | |
| int h_af = h->addr->ai_family; | |
| /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ | |
| @@ -1143,7 +1144,8 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf, | |
| #endif | |
| ) { | |
| result = bindlocal(data, cf->conn, ctx->sock, ctx->addr.family, | |
| - | Curl_ipv6_scope(&ctx->addr.curl_sa_addr)); |
| + | Curl_ipv6_scope(&ctx->addr.curl_sa_addr), |
| + | ctx->transport); |
| if(result) { | |
| if(result == CURLE_UNSUPPORTED_PROTOCOL) { | |
| /* The address family is not supported on this interface. | |
| @@ -700,6 +700,18 @@ unsigned char Curl_conn_get_transport(struct Curl_easy *data, | |
| return Curl_conn_cf_get_transport(cf, data); | |
| } | |
| + | int Curl_socktype_for_transport(uint8_t transport) |
| + | { |
| + | switch(transport) { |
| + | case TRNSPRT_TCP: |
| + | return SOCK_STREAM; |
| + | case TRNSPRT_UNIX: |
| + | return SOCK_STREAM; |
| + | default: /* UDP and QUIC */ |
| + | return SOCK_DGRAM; |
| + | } |
| + | } |
| + | |
| const char *Curl_conn_get_alpn_negotiated(struct Curl_easy *data, | |
| struct connectdata *conn) | |
| { | |
| @@ -345,6 +345,8 @@ bool Curl_conn_cf_needs_flush(struct Curl_cfilter *cf, | |
| unsigned char Curl_conn_cf_get_transport(struct Curl_cfilter *cf, | |
| struct Curl_easy *data); | |
| + | int Curl_socktype_for_transport(uint8_t transport); |
| + | |
| const char *Curl_conn_cf_get_alpn_negotiated(struct Curl_cfilter *cf, | |
| struct Curl_easy *data); | |
| @@ -779,8 +779,9 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) | |
| if(multi->in_callback) | |
| return CURLE_RECURSIVE_API_CALL; | |
| - | /* Copy the MAXCONNECTS option to the multi handle */ |
| + | /* Copy relevant easy options to the multi handle */ |
| curl_multi_setopt(multi, CURLMOPT_MAXCONNECTS, (long)data->set.maxconnects); | |
| + | curl_multi_setopt(multi, CURLMOPT_QUICK_EXIT, (long)data->set.quick_exit); |
| data->multi_easy = NULL; /* pretend it does not exist */ | |
| mresult = curl_multi_add_handle(multi, data); | |
| @@ -1063,6 +1063,7 @@ static CURLcode ftp_port_resolve_host(struct Curl_easy *data, | |
| *resp = NULL; | |
| result = Curl_resolv_blocking(data, host, 0, conn->ip_version, | |
| + | Curl_conn_get_transport(data, conn), |
| dns_entryp); | |
| if(result) | |
| failf(data, "failed to resolve the address provided to PORT: %s", host); | |
| @@ -2163,6 +2164,7 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, | |
| (void)Curl_resolv_blocking(data, host_name, ipquad.remote_port, | |
| is_ipv6 ? CURL_IPRESOLVE_V6 : CURL_IPRESOLVE_V4, | |
| + | Curl_conn_get_transport(data, conn), |
| &dns); | |
| /* we connect to the proxy's port */ | |
| connectport = (unsigned short)ipquad.remote_port; | |
| @@ -2187,7 +2189,9 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, | |
| goto error; | |
| } | |
| - | (void)Curl_resolv_blocking(data, newhost, newport, conn->ip_version, &dns); |
| + | (void)Curl_resolv_blocking(data, newhost, newport, conn->ip_version, |
| + | Curl_conn_get_transport(data, conn), |
| + | &dns); |
| connectport = newport; /* we connect to the remote port */ | |
| if(!dns) { | |
| @@ -373,7 +373,9 @@ static bool can_resolve_ip_version(struct Curl_easy *data, int ip_version) | |
| static CURLcode hostip_async_new(struct Curl_easy *data, | |
| const char *hostname, | |
| uint16_t port, | |
| - | uint8_t ip_version) |
| + | uint8_t ip_version, |
| + | uint8_t transport, |
| + | timediff_t timeout_ms) |
| { | |
| struct Curl_resolv_async *async; | |
| size_t hostlen = strlen(hostname); | |
| @@ -389,6 +391,9 @@ static CURLcode hostip_async_new(struct Curl_easy *data, | |
| async->id = data->state.next_async_id++; | |
| async->port = port; | |
| async->ip_version = ip_version; | |
| + | async->transport = transport; |
| + | async->start = *Curl_pgrs_now(data); |
| + | async->timeout_ms = timeout_ms; |
| if(hostlen) | |
| memcpy(async->hostname, hostname, hostlen); | |
| @@ -401,6 +406,8 @@ static CURLcode hostip_resolv(struct Curl_easy *data, | |
| const char *hostname, | |
| uint16_t port, | |
| uint8_t ip_version, | |
| + | uint8_t transport, |
| + | timediff_t timeout_ms, |
| bool allowDOH, | |
| struct Curl_dns_entry **entry) | |
| { | |
| @@ -415,6 +422,8 @@ static CURLcode hostip_resolv(struct Curl_easy *data, | |
| #ifdef USE_CURL_ASYNC | |
| if(data->state.async) | |
| Curl_async_destroy(data); | |
| + | #else |
| + | (void)timeout_ms; |
| #endif | |
| #ifndef CURL_DISABLE_DOH | |
| @@ -463,7 +472,8 @@ static CURLcode hostip_resolv(struct Curl_easy *data, | |
| int st; | |
| #ifdef CURLRES_ASYNCH | |
| if(!data->state.async) { | |
| - | result = hostip_async_new(data, hostname, port, ip_version); |
| + | result = hostip_async_new(data, hostname, port, ip_version, |
| + | transport, timeout_ms); |
| if(result) | |
| goto error; | |
| } | |
| @@ -501,7 +511,8 @@ static CURLcode hostip_resolv(struct Curl_easy *data, | |
| #ifndef CURL_DISABLE_DOH | |
| else if(!Curl_is_ipaddr(hostname) && allowDOH && data->set.doh) { | |
| if(!data->state.async) { | |
| - | result = hostip_async_new(data, hostname, port, ip_version); |
| + | result = hostip_async_new(data, hostname, port, ip_version, |
| + | transport, timeout_ms); |
| if(result) | |
| goto error; | |
| } | |
| @@ -518,7 +529,8 @@ static CURLcode hostip_resolv(struct Curl_easy *data, | |
| #ifdef CURLRES_ASYNCH | |
| if(!data->state.async) { | |
| - | result = hostip_async_new(data, hostname, port, ip_version); |
| + | result = hostip_async_new(data, hostname, port, ip_version, |
| + | transport, timeout_ms); |
| if(result) | |
| goto error; | |
| } | |
| @@ -526,7 +538,7 @@ static CURLcode hostip_resolv(struct Curl_easy *data, | |
| respwait = TRUE; | |
| #else | |
| respwait = FALSE; /* no async waiting here */ | |
| - | addr = Curl_sync_getaddrinfo(data, hostname, port, ip_version); |
| + | addr = Curl_sync_getaddrinfo(data, hostname, port, ip_version, transport); |
| if(addr) | |
| result = CURLE_OK; | |
| #endif | |
| @@ -577,13 +589,15 @@ CURLcode Curl_resolv_blocking(struct Curl_easy *data, | |
| const char *hostname, | |
| uint16_t port, | |
| uint8_t ip_version, | |
| + | uint8_t transport, |
| struct Curl_dns_entry **pdns) | |
| { | |
| CURLcode result; | |
| DEBUGASSERT(hostname && *hostname); | |
| *pdns = NULL; | |
| /* We cannot do a blocking resolve using DoH currently */ | |
| - | result = hostip_resolv(data, hostname, port, ip_version, FALSE, pdns); |
| + | result = hostip_resolv(data, hostname, port, ip_version, |
| + | transport, 0, FALSE, pdns); |
| switch(result) { | |
| case CURLE_OK: | |
| DEBUGASSERT(*pdns); | |
| @@ -619,7 +633,8 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, | |
| const char *hostname, | |
| uint16_t port, | |
| uint8_t ip_version, | |
| - | timediff_t timeoutms, |
| + | uint8_t transport, |
| + | timediff_t timeout_ms, |
| struct Curl_dns_entry **entry) | |
| { | |
| #ifdef HAVE_SIGACTION | |
| @@ -636,14 +651,14 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, | |
| CURLcode result; | |
| DEBUGASSERT(hostname && *hostname); | |
| - | DEBUGASSERT(timeoutms > 0); |
| + | DEBUGASSERT(timeout_ms > 0); |
| DEBUGASSERT(!data->set.no_signal); | |
| #ifndef CURL_DISABLE_DOH | |
| DEBUGASSERT(!data->set.doh); | |
| #endif | |
| *entry = NULL; | |
| - | timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms; |
| + | timeout = (timeout_ms > LONG_MAX) ? LONG_MAX : (long)timeout_ms; |
| if(timeout < 1000) { | |
| /* The alarm() function only provides integer second resolution, so if | |
| we want to wait less than one second we must bail out already now. */ | |
| @@ -696,7 +711,8 @@ static CURLcode resolv_alarm_timeout(struct Curl_easy *data, | |
| /* Perform the actual name resolution. This might be interrupted by an | |
| * alarm if it takes too long. */ | |
| - | result = hostip_resolv(data, hostname, port, ip_version, TRUE, entry); |
| + | result = hostip_resolv(data, hostname, port, ip_version, transport, |
| + | timeout_ms, TRUE, entry); |
| clean_up: | |
| if(!prev_alarm) | |
| @@ -772,33 +788,35 @@ CURLcode Curl_resolv(struct Curl_easy *data, | |
| const char *hostname, | |
| uint16_t port, | |
| uint8_t ip_version, | |
| - | timediff_t timeoutms, |
| + | uint8_t transport, |
| + | timediff_t timeout_ms, |
| struct Curl_dns_entry **entry) | |
| { | |
| DEBUGASSERT(hostname && *hostname); | |
| *entry = NULL; | |
| - | if(timeoutms < 0) |
| + | if(timeout_ms < 0) |
| /* got an already expired timeout */ | |
| return CURLE_OPERATION_TIMEDOUT; | |
| #ifdef USE_ALARM_TIMEOUT | |
| - | if(timeoutms && data->set.no_signal) { |
| + | if(timeout_ms && data->set.no_signal) { |
| /* Cannot use ALARM when signals are disabled */ | |
| - | timeoutms = 0; |
| + | timeout_ms = 0; |
| } | |
| - | if(timeoutms && !Curl_doh_wanted(data)) { |
| + | if(timeout_ms && !Curl_doh_wanted(data)) { |
| return resolv_alarm_timeout(data, hostname, port, ip_version, | |
| - | timeoutms, entry); |
| + | transport, timeout_ms, entry); |
| } | |
| #endif /* !USE_ALARM_TIMEOUT */ | |
| #ifndef CURLRES_ASYNCH | |
| - | if(timeoutms) |
| + | if(timeout_ms) |
| infof(data, "timeout on name lookup is not supported"); | |
| #endif | |
| - | return hostip_resolv(data, hostname, port, ip_version, TRUE, entry); |
| + | return hostip_resolv(data, hostname, port, ip_version, transport, |
| + | timeout_ms, TRUE, entry); |
| } | |
| @@ -91,13 +91,15 @@ CURLcode Curl_resolv(struct Curl_easy *data, | |
| const char *hostname, | |
| uint16_t port, | |
| uint8_t ip_version, | |
| - | timediff_t timeoutms, |
| + | uint8_t transport, |
| + | timediff_t timeout_ms, |
| struct Curl_dns_entry **pdns); | |
| CURLcode Curl_resolv_blocking(struct Curl_easy *data, | |
| const char *hostname, | |
| uint16_t port, | |
| uint8_t ip_version, | |
| + | uint8_t transport, |
| struct Curl_dns_entry **pdns); | |
| CURLcode Curl_resolv_timeout(struct Curl_easy *data, | |
| @@ -127,7 +129,8 @@ CURLcode Curl_resolver_error(struct Curl_easy *data, const char *detail); | |
| struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, | |
| const char *hostname, | |
| uint16_t port, | |
| - | uint8_t ip_version); |
| + | uint8_t ip_version, |
| + | uint8_t transport); |
| #endif | |
| @@ -70,11 +70,13 @@ | |
| struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, | |
| const char *hostname, | |
| uint16_t port, | |
| - | uint8_t ip_version) |
| + | uint8_t ip_version, |
| + | uint8_t transport) |
| { | |
| struct Curl_addrinfo *ai = NULL; | |
| (void)ip_version; | |
| + | (void)transport; |
| ai = Curl_ipv4_resolve_r(hostname, port); | |
| if(!ai) | |
| @@ -65,7 +65,8 @@ | |
| struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, | |
| const char *hostname, | |
| uint16_t port, | |
| - | uint8_t ip_version) |
| + | uint8_t ip_version, |
| + | uint8_t transport) |
| { | |
| struct addrinfo hints; | |
| struct Curl_addrinfo *res; | |
| @@ -83,8 +84,7 @@ struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data, | |
| memset(&hints, 0, sizeof(hints)); | |
| hints.ai_family = pf; | |
| - | hints.ai_socktype = |
| - | (Curl_conn_get_transport(data, data->conn) == TRNSPRT_TCP) ? |
| + | hints.ai_socktype = (transport == TRNSPRT_TCP) ? |
| SOCK_STREAM : SOCK_DGRAM; | |
| #ifndef USE_RESOLVE_ON_IPS | |
| @@ -307,10 +307,24 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size, | |
| goto error; | |
| #endif | |
| + | #ifdef USE_RESOLV_THREADED |
| + | if(xfer_table_size < CURL_XFER_TABLE_SIZE) { /* easy multi */ |
| + | if(Curl_async_thrdd_multi_init(multi, 0, 2, 10)) |
| + | goto error; |
| + | } |
| + | else { /* real multi handle */ |
| + | if(Curl_async_thrdd_multi_init(multi, 0, 20, 2000)) |
| + | goto error; |
| + | } |
| + | #endif |
| + | |
| return multi; | |
| error: | |
| + | #ifdef USE_RESOLV_THREADED |
| + | Curl_async_thrdd_multi_destroy(multi, TRUE); |
| + | #endif |
| Curl_multi_ev_cleanup(multi); | |
| Curl_hash_destroy(&multi->proto_hash); | |
| Curl_dnscache_destroy(&multi->dnscache); | |
| @@ -2522,6 +2536,9 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, | |
| Curl_uint32_bset_remove(&multi->dirty, data->mid); | |
| if(data == multi->admin) { | |
| + | #ifdef USE_RESOLV_THREADED |
| + | Curl_async_thrdd_multi_process(multi); |
| + | #endif |
| Curl_cshutdn_perform(&multi->cshutdn, multi->admin, sigpipe_ctx); | |
| return CURLM_OK; | |
| } | |
| @@ -2951,6 +2968,9 @@ CURLMcode curl_multi_cleanup(CURLM *m) | |
| } while(Curl_uint32_tbl_next(&multi->xfers, mid, &mid, &entry)); | |
| } | |
| + | #ifdef USE_RESOLV_THREADED |
| + | Curl_async_thrdd_multi_destroy(multi, !multi->quick_exit); |
| + | #endif |
| Curl_cpool_destroy(&multi->cpool); | |
| Curl_cshutdn_destroy(&multi->cshutdn, multi->admin); | |
| if(multi->admin) { | |
| @@ -3347,6 +3367,34 @@ CURLMcode curl_multi_setopt(CURLM *m, CURLMoption option, ...) | |
| case CURLMOPT_NOTIFYDATA: | |
| multi->ntfy.ntfy_cb_data = va_arg(param, void *); | |
| break; | |
| + | case CURLMOPT_RESOLVE_THREADS_MAX: |
| + | #ifdef USE_RESOLV_THREADED |
| + | uarg = va_arg(param, long); |
| + | if((uarg <= 0) || (uarg > UINT32_MAX)) |
| + | mresult = CURLM_BAD_FUNCTION_ARGUMENT; |
| + | else { |
| + | CURLcode result = Curl_async_thrdd_multi_set_props( |
| + | multi, 0, (uint32_t)uarg, 2000); |
| + | switch(result) { |
| + | case CURLE_OK: |
| + | mresult = CURLM_OK; |
| + | break; |
| + | case CURLE_BAD_FUNCTION_ARGUMENT: |
| + | mresult = CURLM_BAD_FUNCTION_ARGUMENT; |
| + | break; |
| + | case CURLE_OUT_OF_MEMORY: |
| + | mresult = CURLM_OUT_OF_MEMORY; |
| + | break; |
| + | default: |
| + | mresult = CURLM_INTERNAL_ERROR; |
| + | break; |
| + | } |
| + | } |
| + | #endif |
| + | break; |
| + | case CURLMOPT_QUICK_EXIT: |
| + | multi->quick_exit = va_arg(param, long) ? 1 : 0; |
| + | break; |
| default: | |
| mresult = CURLM_UNKNOWN_OPTION; | |
| break; | |
| @@ -109,6 +109,9 @@ struct Curl_multi { | |
| struct Curl_dnscache dnscache; /* DNS cache */ | |
| struct Curl_ssl_scache *ssl_scache; /* TLS session pool */ | |
| + | #ifdef USE_RESOLV_THREADED |
| + | struct curl_thrdq *resolv_thrdq; |
| + | #endif |
| #ifdef USE_LIBPSL | |
| /* PSL cache. */ | |
| @@ -186,6 +189,7 @@ struct Curl_multi { | |
| BIT(xfer_buf_borrowed); /* xfer_buf is currently being borrowed */ | |
| BIT(xfer_ulbuf_borrowed); /* xfer_ulbuf is currently being borrowed */ | |
| BIT(xfer_sockbuf_borrowed); /* xfer_sockbuf is currently being borrowed */ | |
| + | BIT(quick_exit); /* do not join threads on cleanup */ |
| #ifdef DEBUGBUILD | |
| BIT(warned); /* true after user warned of DEBUGBUILD */ | |
| #endif | |
| @@ -324,7 +324,9 @@ static CURLproxycode socks4_resolving(struct socks_state *sx, | |
| DEBUGASSERT(sx->hostname && *sx->hostname); | |
| result = Curl_resolv(data, sx->hostname, sx->remote_port, | |
| - | cf->conn->ip_version, 0, &dns); |
| + | cf->conn->ip_version, |
| + | Curl_conn_cf_get_transport(cf, data), |
| + | 0, &dns); |
| if(result == CURLE_AGAIN) { | |
| CURL_TRC_CF(data, cf, "SOCKS4 non-blocking resolve of %s", sx->hostname); | |
| return CURLPX_OK; | |
| @@ -853,7 +855,9 @@ static CURLproxycode socks5_resolving(struct socks_state *sx, | |
| DEBUGASSERT(sx->hostname && *sx->hostname); | |
| result = Curl_resolv(data, sx->hostname, sx->remote_port, | |
| - | cf->conn->ip_version, 0, &dns); |
| + | cf->conn->ip_version, |
| + | Curl_conn_cf_get_transport(cf, data), |
| + | 0, &dns); |
| if(result == CURLE_AGAIN) { | |
| CURL_TRC_CF(data, cf, "SOCKS5 non-blocking resolve of %s", sx->hostname); | |
| return CURLPX_OK; | |
| @@ -121,7 +121,8 @@ static CURL_THREAD_RETURN_T CURL_STDCALL thrdslot_run(void *arg) | |
| tpool->fn_return(item, tpool->aborted ? NULL : tpool->fn_user_data); | |
| } | |
| - | if(tpool->aborted) |
| + | if(tpool->aborted || |
| + | (Curl_llist_count(&tpool->slots) > tpool->max_threads)) |
| goto out; | |
| tslot->idle = TRUE; | |
| @@ -235,6 +236,63 @@ static bool thrdpool_unlink(struct curl_thrdpool *tpool, bool locked) | |
| return TRUE; | |
| } | |
| + | static CURLcode thrdpool_signal(struct curl_thrdpool *tpool, |
| + | uint32_t nthreads) |
| + | { |
| + | struct Curl_llist_node *e, *n; |
| + | CURLcode result = CURLE_OK; |
| + | |
| + | DEBUGASSERT(!tpool->aborted); |
| + | thrdpool_join_zombies(tpool); |
| + | |
| + | for(e = Curl_llist_head(&tpool->slots); e && nthreads; e = n) { |
| + | struct thrdslot *tslot = Curl_node_elem(e); |
| + | n = Curl_node_next(e); |
| + | if(tslot->idle) { |
| + | Curl_cond_signal(&tslot->await); |
| + | --nthreads; |
| + | } |
| + | else if(!tslot->starttime.tv_sec && !tslot->starttime.tv_usec) { |
| + | /* starting thread, queries for work soon. */ |
| + | --nthreads; |
| + | } |
| + | } |
| + | |
| + | while(nthreads && !result && |
| + | Curl_llist_count(&tpool->slots) < tpool->max_threads) { |
| + | result = thrdslot_start(tpool); |
| + | if(result) |
| + | break; |
| + | --nthreads; |
| + | } |
| + | |
| + | return result; |
| + | } |
| + | |
| + | CURLcode Curl_thrdpool_set_props(struct curl_thrdpool *tpool, |
| + | uint32_t min_threads, |
| + | uint32_t max_threads, |
| + | uint32_t idle_time_ms) |
| + | { |
| + | CURLcode result = CURLE_OK; |
| + | size_t running; |
| + | |
| + | if(!max_threads || (min_threads > max_threads)) |
| + | return CURLE_BAD_FUNCTION_ARGUMENT; |
| + | |
| + | Curl_mutex_acquire(&tpool->lock); |
| + | tpool->min_threads = min_threads; |
| + | tpool->max_threads = max_threads; |
| + | tpool->idle_time_ms = idle_time_ms; |
| + | running = Curl_llist_count(&tpool->slots); |
| + | if(tpool->min_threads > running) { |
| + | result = thrdpool_signal(tpool, tpool->min_threads - (uint32_t)running); |
| + | } |
| + | Curl_mutex_release(&tpool->lock); |
| + | |
| + | return result; |
| + | } |
| + | |
| CURLcode Curl_thrdpool_create(struct curl_thrdpool **ptpool, | |
| const char *name, | |
| uint32_t min_threads, | |
| @@ -257,9 +315,6 @@ CURLcode Curl_thrdpool_create(struct curl_thrdpool **ptpool, | |
| Curl_cond_init(&tpool->await); | |
| Curl_llist_init(&tpool->slots, NULL); | |
| Curl_llist_init(&tpool->zombies, NULL); | |
| - | tpool->min_threads = min_threads; |
| - | tpool->max_threads = max_threads; |
| - | tpool->idle_time_ms = idle_time_ms; |
| tpool->fn_take = fn_take; | |
| tpool->fn_process = fn_process; | |
| tpool->fn_return = fn_return; | |
| @@ -269,10 +324,8 @@ CURLcode Curl_thrdpool_create(struct curl_thrdpool **ptpool, | |
| if(!tpool->name) | |
| goto out; | |
| - | if(tpool->min_threads) |
| - | result = Curl_thrdpool_signal(tpool, tpool->min_threads); |
| - | else |
| - | result = CURLE_OK; |
| + | result = Curl_thrdpool_set_props(tpool, min_threads, max_threads, |
| + | idle_time_ms); |
| out: | |
| if(result && tpool) { | |
| @@ -316,35 +369,10 @@ void Curl_thrdpool_destroy(struct curl_thrdpool *tpool, bool join) | |
| CURLcode Curl_thrdpool_signal(struct curl_thrdpool *tpool, uint32_t nthreads) | |
| { | |
| - | struct Curl_llist_node *e, *n; |
| - | CURLcode result = CURLE_OK; |
| + | CURLcode result; |
| Curl_mutex_acquire(&tpool->lock); | |
| - | DEBUGASSERT(!tpool->aborted); |
| - | |
| - | thrdpool_join_zombies(tpool); |
| - | |
| - | for(e = Curl_llist_head(&tpool->slots); e && nthreads; e = n) { |
| - | struct thrdslot *tslot = Curl_node_elem(e); |
| - | n = Curl_node_next(e); |
| - | if(tslot->idle) { |
| - | Curl_cond_signal(&tslot->await); |
| - | --nthreads; |
| - | } |
| - | else if(!tslot->starttime.tv_sec && !tslot->starttime.tv_usec) { |
| - | /* starting thread, queries for work soon. */ |
| - | --nthreads; |
| - | } |
| - | } |
| - | |
| - | while(nthreads && !result && |
| - | Curl_llist_count(&tpool->slots) < tpool->max_threads) { |
| - | result = thrdslot_start(tpool); |
| - | if(result) |
| - | break; |
| - | --nthreads; |
| - | } |
| - | |
| + | result = thrdpool_signal(tpool, nthreads); |
| Curl_mutex_release(&tpool->lock); | |
| return result; | |
| } | |
| @@ -91,6 +91,12 @@ CURLcode Curl_thrdpool_signal(struct curl_thrdpool *tpool, uint32_t nthreads); | |
| CURLcode Curl_thrdpool_await_idle(struct curl_thrdpool *tpool, | |
| uint32_t timeout_ms); | |
| + | /* Change the properties of a threadpool. */ |
| + | CURLcode Curl_thrdpool_set_props(struct curl_thrdpool *tpool, |
| + | uint32_t min_threads, |
| + | uint32_t max_threads, |
| + | uint32_t idle_time_ms); |
| + | |
| #ifdef CURLVERBOSE | |
| void Curl_thrdpool_trace(struct curl_thrdpool *tpool, | |
| struct Curl_easy *data, | |
| @@ -282,16 +282,20 @@ CURLcode Curl_thrdq_send(struct curl_thrdq *tqueue, void *item, | |
| result = CURLE_OUT_OF_MEMORY; | |
| goto out; | |
| } | |
| + | item = NULL; |
| Curl_llist_append(&tqueue->sendq, qitem, &qitem->node); | |
| - | result = CURLE_OK; |
| signals = Curl_llist_count(&tqueue->sendq); | |
| + | result = CURLE_OK; |
| } | |
| out: | |
| Curl_mutex_release(&tqueue->lock); | |
| - | /* Signal thread pool unlocked to avoid deadlocks */ |
| + | /* Signal thread pool unlocked to avoid deadlocks. Since we added |
| + | * item to the queue already, it might have been taken for processing |
| + | * already. Any error in signalling the pool cannot be reported to |
| + | * the caller since it needs to give up ownership of item. */ |
| if(!result && signals) | |
| - | result = Curl_thrdpool_signal(tqueue->tpool, (uint32_t)signals); |
| + | (void)Curl_thrdpool_signal(tqueue->tpool, (uint32_t)signals); |
| return result; | |
| } | |
| @@ -357,6 +361,27 @@ CURLcode Curl_thrdq_await_done(struct curl_thrdq *tqueue, | |
| return Curl_thrdpool_await_idle(tqueue->tpool, timeout_ms); | |
| } | |
| + | CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue, |
| + | uint32_t max_len, |
| + | uint32_t min_threads, |
| + | uint32_t max_threads, |
| + | uint32_t idle_time_ms) |
| + | { |
| + | CURLcode result; |
| + | size_t signals; |
| + | |
| + | Curl_mutex_acquire(&tqueue->lock); |
| + | tqueue->send_max_len = max_len; |
| + | signals = Curl_llist_count(&tqueue->sendq); |
| + | Curl_mutex_release(&tqueue->lock); |
| + | |
| + | result = Curl_thrdpool_set_props(tqueue->tpool, min_threads, |
| + | max_threads, idle_time_ms); |
| + | if(!result && signals) |
| + | result = Curl_thrdpool_signal(tqueue->tpool, (uint32_t)signals); |
| + | return result; |
| + | } |
| + | |
| #ifdef CURLVERBOSE | |
| void Curl_thrdq_trace(struct curl_thrdq *tqueue, | |
| struct Curl_easy *data, | |
| @@ -103,6 +103,12 @@ void Curl_thrdq_clear(struct curl_thrdq *tqueue, | |
| CURLcode Curl_thrdq_await_done(struct curl_thrdq *tqueue, | |
| uint32_t timeout_ms); | |
| + | CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue, |
| + | uint32_t max_len, /* 0 for unlimited */ |
| + | uint32_t min_threads, |
| + | uint32_t max_threads, |
| + | uint32_t idle_time_ms); |
| + | |
| #ifdef CURLVERBOSE | |
| void Curl_thrdq_trace(struct curl_thrdq *tqueue, | |
| struct Curl_easy *data, | |
| @@ -3039,7 +3039,8 @@ static CURLcode resolve_server(struct Curl_easy *data, | |
| } | |
| result = Curl_resolv(data, ehost->name, eport, | |
| - | conn->ip_version, timeout_ms, pdns); |
| + | conn->ip_version, conn->transport_wanted, |
| + | timeout_ms, pdns); |
| DEBUGASSERT(!result || !*pdns); | |
| if(!result) { /* resolved right away, either sync or from dnscache */ | |
| DEBUGASSERT(*pdns); | |
| @@ -1867,6 +1867,9 @@ static CURLcode parallel_transfers(CURLSH *share) | |
| if(!s->multi) | |
| return CURLE_OUT_OF_MEMORY; | |
| + | #ifndef DEBUGBUILD |
| + | (void)curl_multi_setopt(s->multi, CURLMOPT_QUICK_EXIT, 1L); |
| + | #endif |
| (void)curl_multi_setopt(s->multi, CURLMOPT_NOTIFYFUNCTION, mnotify); | |
| (void)curl_multi_setopt(s->multi, CURLMOPT_NOTIFYDATA, s); | |
| (void)curl_multi_notify_enable(s->multi, CURLMNOTIFY_INFO_READ); | |
| @@ -72,7 +72,7 @@ via: 1.1 nghttpx | |
| s/^server: nghttpx.*\r?\n// | |
| </stripfile> | |
| <limits> | |
| - | Allocations: 155 |
| + | Allocations: 160 |
| Maximum allocated: 1800000 | |
| </limits> | |
| </verify> | |
| @@ -63,6 +63,7 @@ EXTRA_DIST = \ | |
| test_18_methods.py \ | |
| test_19_shutdown.py \ | |
| test_20_websockets.py \ | |
| + | test_21_resolve.py \ |
| test_30_vsftpd.py \ | |
| test_31_vsftpds.py \ | |
| test_32_ftps_vsftpd.py \ | |
| @@ -0,0 +1,124 @@ | |
| + | #!/usr/bin/env python3 |
| + | # -*- coding: utf-8 -*- |
| + | #*************************************************************************** |
| + | # _ _ ____ _ |
| + | # Project ___| | | | _ \| | |
| + | # / __| | | | |_) | | |
| + | # | (__| |_| | _ <| |___ |
| + | # \___|\___/|_| \_\_____| |
| + | # |
| + | # Copyright (C) Daniel Stenberg, <[email protected]>, 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.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. |
| + | # |
| + | # SPDX-License-Identifier: curl |
| + | # |
| + | ########################################################################### |
| + | # |
| + | import logging |
| + | import os |
| + | from datetime import timedelta |
| + | import pytest |
| + | |
| + | from testenv import Env, CurlClient, LocalClient |
| + | |
| + | |
| + | log = logging.getLogger(__name__) |
| + | |
| + | |
| + | @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug") |
| + | @pytest.mark.skipif(condition=Env.curl_uses_lib('c-ares'), reason="c-ares resolver skipped") |
| + | @pytest.mark.skipif(condition=not Env.curl_has_feature('AsynchDNS'), reason="needs AsynchDNS") |
| + | class TestResolve: |
| + | |
| + | @pytest.fixture(autouse=True, scope='class') |
| + | def _class_scope(self, env, httpd): |
| + | indir = httpd.docs_dir |
| + | env.make_data_file(indir=indir, fname="data-0k", fsize=0) |
| + | |
| + | # use .invalid host name that should never resolv |
| + | def test_21_01_resolv_invalid_one(self, env: Env, httpd, nghttpx): |
| + | count = 1 |
| + | run_env = os.environ.copy() |
| + | run_env['CURL_DBG_RESOLV_FAIL_DELAY'] = '5' |
| + | curl = CurlClient(env=env, run_env=run_env, force_resolv=False) |
| + | url = f'https://test-{count}.http.curl.invalid/' |
| + | r = curl.http_download(urls=[url], with_stats=True) |
| + | r.check_exit_code(6) |
| + | r.check_stats(count=count, http_status=0, exitcode=6) |
| + | |
| + | # use .invalid host name, one after the other |
| + | @pytest.mark.parametrize("delay_ms", [1, 50]) |
| + | def test_21_02_resolv_invalid_serial(self, env: Env, delay_ms, httpd, nghttpx): |
| + | count = 10 |
| + | run_env = os.environ.copy() |
| + | run_env['CURL_DBG_RESOLV_FAIL_DELAY'] = f'{delay_ms}' |
| + | curl = CurlClient(env=env, run_env=run_env, force_resolv=False) |
| + | urls = [ f'https://test-{i}.http.curl.invalid/' for i in range(count)] |
| + | r = curl.http_download(urls=urls, with_stats=True) |
| + | r.check_exit_code(6) |
| + | r.check_stats(count=count, http_status=0, exitcode=6) |
| + | |
| + | # use .invalid host name, parallel |
| + | @pytest.mark.parametrize("delay_ms", [1, 50]) |
| + | def test_21_03_resolv_invalid_parallel(self, env: Env, delay_ms, httpd, nghttpx): |
| + | count = 20 |
| + | run_env = os.environ.copy() |
| + | run_env['CURL_DBG_RESOLV_FAIL_DELAY'] = f'{delay_ms}' |
| + | curl = CurlClient(env=env, run_env=run_env, force_resolv=False) |
| + | urls = [ f'https://test-{i}.http.curl.invalid/' for i in range(count)] |
| + | r = curl.http_download(urls=urls, with_stats=True, extra_args=[ |
| + | '--parallel' |
| + | ]) |
| + | r.check_exit_code(6) |
| + | r.check_stats(count=count, http_status=0, exitcode=6) |
| + | |
| + | # resolve first url with ipv6 only and fail that, resolve second |
| + | # with ipv*, should succeed. |
| + | def test_21_04_resolv_inv_v6(self, env: Env, httpd): |
| + | count = 2 |
| + | run_env = os.environ.copy() |
| + | run_env['CURL_DBG_RESOLV_FAIL_IPV6'] = '1' |
| + | url = f'https://localhost:{env.https_port}/' |
| + | client = LocalClient(name='cli_hx_download', env=env, run_env=run_env) |
| + | if not client.exists(): |
| + | pytest.skip(f'example client not built: {client.name}') |
| + | dfiles = [client.download_file(i) for i in range(count)] |
| + | self._clean_files(dfiles) |
| + | # let the first URL resolve via ipv6 only, which we force to fail |
| + | r = client.run(args=[ |
| + | '-n', f'{count}', '-6', '-C', env.ca.cert_file, url |
| + | ]) |
| + | r.check_exit_code(6) |
| + | assert not os.path.exists(dfiles[0]) |
| + | assert os.path.exists(dfiles[1]) |
| + | |
| + | # use .invalid host name, parallel, single resolve thread |
| + | def test_21_05_resolv_single_thread(self, env: Env, httpd, nghttpx): |
| + | count = 10 |
| + | delay_ms = 50 |
| + | run_env = os.environ.copy() |
| + | run_env['CURL_DBG_RESOLV_FAIL_DELAY'] = f'{delay_ms}' |
| + | run_env['CURL_DBG_RESOLV_MAX_THREADS'] = '1' |
| + | curl = CurlClient(env=env, run_env=run_env, force_resolv=False) |
| + | urls = [ f'https://test-{i}.http.curl.invalid/' for i in range(count)] |
| + | r = curl.http_download(urls=urls, with_stats=True, extra_args=[ |
| + | '--parallel', '-6' |
| + | ]) |
| + | r.check_exit_code(6) |
| + | r.check_stats(count=count, http_status=0, exitcode=6) |
| + | assert r.duration > timedelta(milliseconds=count * delay_ms), f'{r}' |
| + | |
| + | def _clean_files(self, files): |
| + | for file in files: |
| + | if os.path.exists(file): |
| + | os.remove(file) |
| @@ -630,6 +630,7 @@ class CurlClient: | |
| with_dtrace: bool = False, | |
| with_perf: bool = False, | |
| with_flame: bool = False, | |
| + | force_resolv: bool = True, |
| socks_args: Optional[List[str]] = None): | |
| self.env = env | |
| self._timeout = timeout if timeout else env.test_timeout | |
| @@ -659,6 +660,7 @@ class CurlClient: | |
| self._silent = silent | |
| self._run_env = run_env | |
| self._server_addr = server_addr if server_addr else '127.0.0.1' | |
| + | self._force_resolv = force_resolv |
| self._rmrf(self._run_dir) | |
| self._mkpath(self._run_dir) | |
| @@ -1086,7 +1088,6 @@ class CurlClient: | |
| def _raw(self, urls, intext='', timeout=None, options=None, insecure=False, | |
| alpn_proto: Optional[str] = None, | |
| url_options=None, | |
| - | force_resolve=True, |
| with_stats=False, | |
| with_headers=True, | |
| def_tracing=True, | |
| @@ -1094,9 +1095,8 @@ class CurlClient: | |
| with_tcpdump=False): | |
| args = self._complete_args( | |
| urls=urls, timeout=timeout, options=options, insecure=insecure, | |
| - | alpn_proto=alpn_proto, force_resolve=force_resolve, |
| - | with_headers=with_headers, def_tracing=def_tracing, |
| - | url_options=url_options) |
| + | alpn_proto=alpn_proto, with_headers=with_headers, |
| + | def_tracing=def_tracing, url_options=url_options) |
| r = self._run(args, intext=intext, with_stats=with_stats, | |
| with_profile=with_profile, with_tcpdump=with_tcpdump) | |
| if r.exit_code == 0 and with_headers: | |
| @@ -1104,8 +1104,7 @@ class CurlClient: | |
| return r | |
| def _complete_args(self, urls, timeout=None, options=None, | |
| - | insecure=False, force_resolve=True, |
| - | alpn_proto: Optional[str] = None, |
| + | insecure=False, alpn_proto: Optional[str] = None, |
| url_options=None, | |
| with_headers: bool = True, | |
| def_tracing: bool = True): | |
| @@ -1115,6 +1114,8 @@ class CurlClient: | |
| if options is not None and '--resolve' in options: | |
| force_resolve = False | |
| + | else: |
| + | force_resolve = self._force_resolv |
| args = [self._curl, "-s", "--path-as-is"] | |
| if 'CURL_TEST_EVENT' in os.environ: | |