async-thrdd: use thread queue for resolving

Use a thread queue and pool for asnyc threaded DNS resolves.
Add pytest test_21_* for verification.

Add `CURLMOPT_RESOLVE_THREADS_MAX` to allow applications to
resize the thread pool used.

Add `CURLMOPT_QUICK_EXIT` to allow applications to skip thread
joins when cleaning up a multi handle. Multi handles in
`curl_easy_perform()` inherit this from `CURLOPT_QUICK_EXIT`.

Add several debug environment variables for testing.

Closes #20936
This commit is contained in:
Stefan Eissing 2026-03-24 12:50:53 +01:00 committed by Daniel Stenberg
parent 507e7be573
commit 39036c9021
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
31 changed files with 998 additions and 614 deletions

View file

@ -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;
}