threaded-resolver: fix shutdown

Changed strategy to start up and terminate resolver thread.

When starting up:

Start the thread with mutex acquired, wait for signal from thread that
it started and has incremented the ref counter. Thread set
pthread_cancel() to disabled before that and only enables cancelling
during resolving itself. This assure that the ref counter is correct and
the unlinking of the resolve context always happens.

When shutting down resolving:

If ref counting shows thread has finished, join it, free everything. If
thread has not finished, try pthread_cancel() (non Windows), but keep
the thread handle around.

When destroying resolving:

Shutdown first, then, if the thread is still there and 'quick_exit' is
not set, join it and free everything. This might occur a delay if
getaddrinfo() hangs and cannot be interrupted by pthread_cancel().

Destroying resolving happens when another resolve is started on an
easy handle or when the easy handle is closed.

Add test795 to check that connect timeout triggers correctly
when resolving is delayed. Add debug env var `CURL_DNS_DELAY_MS`
to simulate delays in resolving.

Fix test1557 to set `quick_exit` and use `xxx.invalid` as domain
instead of `nothing` that was leading to hangers in CI.

Closes #18263
This commit is contained in:
Stefan Eissing 2025-08-20 15:48:20 +02:00 committed by Daniel Stenberg
parent f3488ee3a3
commit 88fc6c491f
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
12 changed files with 391 additions and 124 deletions

View File

@ -83,6 +83,11 @@ When built with c-ares for name resolving, setting this environment variable
to `[IP:port]` makes libcurl use that DNS server instead of the system
default. This is used by the curl test suite.
## `CURL_DNS_DELAY_MS`
Delay the DNS resolve by this many milliseconds. This is used in the test
suite to check proper handling of CURLOPT_CONNECTTIMEOUT(3).
## `CURL_FTP_PWD_STOP`
When set, the first transfer - when using ftp: - returns before sending

View File

@ -107,6 +107,7 @@ void Curl_async_global_cleanup(void)
}
static void async_thrdd_destroy(struct Curl_easy *);
static void async_thrdd_shutdown(struct Curl_easy *);
CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl)
{
@ -115,33 +116,74 @@ CURLcode Curl_async_get_impl(struct Curl_easy *data, void **impl)
return CURLE_OK;
}
/* Destroy context of threaded resolver */
static void addr_ctx_destroy(struct async_thrdd_addr_ctx *addr_ctx)
/* Give up reference to add_ctx */
static void addr_ctx_unlink(struct async_thrdd_addr_ctx **paddr_ctx,
struct Curl_easy *data)
{
if(addr_ctx) {
DEBUGASSERT(!addr_ctx->ref_count);
struct async_thrdd_addr_ctx *addr_ctx = *paddr_ctx;
bool destroy;
(void)data;
if(!addr_ctx)
return;
Curl_mutex_acquire(&addr_ctx->mutx);
DEBUGASSERT(addr_ctx->ref_count);
--addr_ctx->ref_count;
destroy = !addr_ctx->ref_count; /* was the last one */
#ifndef CURL_DISABLE_SOCKETPAIR
if(!destroy) {
if(!data) { /* Called from thread, transfer still waiting on results. */
if(addr_ctx->sock_pair[1] != CURL_SOCKET_BAD) {
#ifdef USE_EVENTFD
const uint64_t buf[1] = { 1 };
#else
const char buf[1] = { 1 };
#endif
/* Thread is done, notify transfer */
if(wakeup_write(addr_ctx->sock_pair[1], buf, sizeof(buf)) < 0) {
/* update sock_error to errno */
addr_ctx->sock_error = SOCKERRNO;
}
}
}
else { /* transfer going away, thread still running */
#ifndef USE_EVENTFD
if(addr_ctx->sock_pair[1] != CURL_SOCKET_BAD) {
wakeup_close(addr_ctx->sock_pair[1]);
addr_ctx->sock_pair[1] = CURL_SOCKET_BAD;
}
#endif
/* Remove socket from event monitoring */
if(addr_ctx->sock_pair[0] != CURL_SOCKET_BAD) {
Curl_multi_will_close(data, addr_ctx->sock_pair[0]);
wakeup_close(addr_ctx->sock_pair[0]);
addr_ctx->sock_pair[0] = CURL_SOCKET_BAD;
}
}
}
#endif
Curl_mutex_release(&addr_ctx->mutx);
if(destroy) {
#ifdef USE_CURL_COND_T
Curl_cond_destroy(&addr_ctx->cond);
#endif
Curl_mutex_destroy(&addr_ctx->mutx);
free(addr_ctx->hostname);
if(addr_ctx->res)
Curl_freeaddrinfo(addr_ctx->res);
#ifndef CURL_DISABLE_SOCKETPAIR
/*
* close one end of the socket pair (may be done in resolver thread);
* the other end (for reading) is always closed in the parent thread.
*/
#ifndef USE_EVENTFD
if(addr_ctx->sock_pair[1] != CURL_SOCKET_BAD) {
wakeup_close(addr_ctx->sock_pair[1]);
}
#endif
#endif
free(addr_ctx);
}
*paddr_ctx = NULL;
}
/* Initialize context for threaded resolver */
static struct async_thrdd_addr_ctx *
addr_ctx_create(const char *hostname, int port,
addr_ctx_create(struct Curl_easy *data,
const char *hostname, int port,
const struct addrinfo *hints)
{
struct async_thrdd_addr_ctx *addr_ctx = calloc(1, sizeof(*addr_ctx));
@ -154,7 +196,7 @@ addr_ctx_create(const char *hostname, int port,
addr_ctx->sock_pair[0] = CURL_SOCKET_BAD;
addr_ctx->sock_pair[1] = CURL_SOCKET_BAD;
#endif
addr_ctx->ref_count = 0;
addr_ctx->ref_count = 1;
#ifdef HAVE_GETADDRINFO
DEBUGASSERT(hints);
@ -164,6 +206,9 @@ addr_ctx_create(const char *hostname, int port,
#endif
Curl_mutex_init(&addr_ctx->mutx);
#ifdef USE_CURL_COND_T
Curl_cond_init(&addr_ctx->cond);
#endif
#ifndef CURL_DISABLE_SOCKETPAIR
/* create socket pair or pipe */
@ -182,20 +227,33 @@ addr_ctx_create(const char *hostname, int port,
if(!addr_ctx->hostname)
goto err_exit;
addr_ctx->ref_count = 1;
return addr_ctx;
err_exit:
#ifndef CURL_DISABLE_SOCKETPAIR
if(addr_ctx->sock_pair[0] != CURL_SOCKET_BAD) {
wakeup_close(addr_ctx->sock_pair[0]);
addr_ctx->sock_pair[0] = CURL_SOCKET_BAD;
}
#endif
addr_ctx_destroy(addr_ctx);
addr_ctx_unlink(&addr_ctx, data);
return NULL;
}
static void async_thrd_cleanup(void *arg)
{
struct async_thrdd_addr_ctx *addr_ctx = arg;
addr_ctx_unlink(&addr_ctx, NULL);
}
static bool asyn_thrd_start(struct async_thrdd_addr_ctx *addr_ctx)
{
Curl_thread_disable_cancel();
Curl_mutex_acquire(&addr_ctx->mutx);
DEBUGASSERT(addr_ctx->ref_count);
++addr_ctx->ref_count;
#ifdef USE_CURL_COND_T
Curl_cond_signal(&addr_ctx->cond);
#endif
Curl_mutex_release(&addr_ctx->mutx);
return TRUE;
}
#ifdef HAVE_GETADDRINFO
/*
@ -207,14 +265,33 @@ err_exit:
static CURL_THREAD_RETURN_T CURL_STDCALL getaddrinfo_thread(void *arg)
{
struct async_thrdd_addr_ctx *addr_ctx = arg;
char service[12];
int rc;
bool all_gone;
msnprintf(service, sizeof(service), "%d", addr_ctx->port);
if(!asyn_thrd_start(addr_ctx))
return 1;
rc = Curl_getaddrinfo_ex(addr_ctx->hostname, service,
&addr_ctx->hints, &addr_ctx->res);
/* clang complains about empty statements and the pthread_cleanup* macros
* are pretty ill defined. */
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wextra-semi-stmt"
#endif
Curl_thread_push_cleanup(async_thrd_cleanup, addr_ctx);
{
char service[12];
Curl_thread_enable_cancel();
#ifdef DEBUGBUILD
Curl_resolve_test_delay();
#endif
msnprintf(service, sizeof(service), "%d", addr_ctx->port);
rc = Curl_getaddrinfo_ex(addr_ctx->hostname, service,
&addr_ctx->hints, &addr_ctx->res);
Curl_thread_disable_cancel();
}
if(rc) {
addr_ctx->sock_error = SOCKERRNO ? SOCKERRNO : rc;
@ -225,31 +302,12 @@ static CURL_THREAD_RETURN_T CURL_STDCALL getaddrinfo_thread(void *arg)
Curl_addrinfo_set_port(addr_ctx->res, addr_ctx->port);
}
Curl_mutex_acquire(&addr_ctx->mutx);
if(addr_ctx->ref_count > 1) {
/* Someone still waiting on our results. */
#ifndef CURL_DISABLE_SOCKETPAIR
if(addr_ctx->sock_pair[1] != CURL_SOCKET_BAD) {
#ifdef USE_EVENTFD
const uint64_t buf[1] = { 1 };
#else
const char buf[1] = { 1 };
Curl_thread_pop_cleanup();
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
/* DNS has been resolved, signal client task */
if(wakeup_write(addr_ctx->sock_pair[1], buf, sizeof(buf)) < 0) {
/* update sock_error to errno */
addr_ctx->sock_error = SOCKERRNO;
}
}
#endif
}
/* thread gives up its reference to the shared data now. */
--addr_ctx->ref_count;
all_gone = !addr_ctx->ref_count;
Curl_mutex_release(&addr_ctx->mutx);
if(all_gone)
addr_ctx_destroy(addr_ctx);
addr_ctx_unlink(&addr_ctx, NULL);
return 0;
}
@ -263,7 +321,25 @@ static CURL_THREAD_RETURN_T CURL_STDCALL gethostbyname_thread(void *arg)
struct async_thrdd_addr_ctx *addr_ctx = arg;
bool all_gone;
addr_ctx->res = Curl_ipv4_resolve_r(addr_ctx->hostname, addr_ctx->port);
if(!asyn_thrd_start(addr_ctx))
return 1;
/* clang complains about empty statements and the pthread_cleanup* macros
* are pretty ill defined. */
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wextra-semi-stmt"
#endif
Curl_thread_push_cleanup(async_thrd_cleanup, addr_ctx);
{
Curl_thread_enable_cancel();
#ifdef DEBUGBUILD
Curl_resolve_test_delay();
#endif
addr_ctx->res = Curl_ipv4_resolve_r(addr_ctx->hostname, addr_ctx->port);
Curl_thread_disable_cancel();
}
if(!addr_ctx->res) {
addr_ctx->sock_error = SOCKERRNO;
@ -271,14 +347,12 @@ static CURL_THREAD_RETURN_T CURL_STDCALL gethostbyname_thread(void *arg)
addr_ctx->sock_error = RESOLVER_ENOMEM;
}
Curl_mutex_acquire(&addr_ctx->mutx);
/* thread gives up its reference to the shared data now. */
--addr_ctx->ref_count;
all_gone = !addr_ctx->ref_count;;
Curl_mutex_release(&addr_ctx->mutx);
if(all_gone)
addr_ctx_destroy(addr_ctx);
Curl_thread_pop_cleanup();
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
async_thrd_cleanup(addr_ctx, 0);
return 0;
}
@ -291,6 +365,7 @@ static void async_thrdd_destroy(struct Curl_easy *data)
{
struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
struct async_thrdd_addr_ctx *addr = thrdd->addr;
#ifdef USE_HTTPSRR_ARES
if(thrdd->rr.channel) {
ares_destroy(thrdd->rr.channel);
@ -299,46 +374,23 @@ static void async_thrdd_destroy(struct Curl_easy *data)
Curl_httpsrr_cleanup(&thrdd->rr.hinfo);
#endif
if(addr) {
#ifndef CURL_DISABLE_SOCKETPAIR
curl_socket_t sock_rd = addr->sock_pair[0];
#endif
bool done;
if(thrdd->addr && (thrdd->addr->thread_hnd != curl_thread_t_null)) {
bool done = TRUE;
/* Release our reference to the data shared with the thread. */
Curl_mutex_acquire(&addr->mutx);
--addr->ref_count;
done = (addr->ref_count <= 1);
Curl_mutex_release(&addr->mutx);
CURL_TRC_DNS(data, "resolve, destroy async data, shared ref=%d",
addr->ref_count);
done = !addr->ref_count;
/* we give up our reference to `addr`, so NULL our pointer.
* coverity analyses this as being a potential unsynched write,
* assuming two calls to this function could be invoked concurrently.
* Which they never are, as the transfer's side runs single-threaded. */
thrdd->addr = NULL;
if(!done) {
if(done)
Curl_thread_join(&addr->thread_hnd);
else {
/* thread is still running. Detach the thread while mutexed, it will
* trigger the cleanup when it releases its reference. */
Curl_thread_destroy(&addr->thread_hnd);
}
Curl_mutex_release(&addr->mutx);
if(done) {
/* thread has released its reference, join it and
* release the memory we shared with it. */
if(addr->thread_hnd != curl_thread_t_null)
Curl_thread_join(&addr->thread_hnd);
addr_ctx_destroy(addr);
}
#ifndef CURL_DISABLE_SOCKETPAIR
/*
* ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE
* before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL
*/
Curl_multi_will_close(data, sock_rd);
wakeup_close(sock_rd);
#endif
}
addr_ctx_unlink(&thrdd->addr, data);
}
#ifdef USE_HTTPSRR_ARES
@ -426,29 +478,36 @@ static bool async_thrdd_init(struct Curl_easy *data,
if(!data->state.async.hostname)
goto err_exit;
addr_ctx = addr_ctx_create(hostname, port, hints);
addr_ctx = addr_ctx_create(data, hostname, port, hints);
if(!addr_ctx)
goto err_exit;
thrdd->addr = addr_ctx;
/* passing addr_ctx to the thread adds a reference */
Curl_mutex_acquire(&addr_ctx->mutx);
DEBUGASSERT(addr_ctx->ref_count == 1);
/* passing addr_ctx to the thread adds a reference */
addr_ctx->start = curlx_now();
++addr_ctx->ref_count;
#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, remove its reference that never happened. */
--addr_ctx->ref_count;
err = errno;
/* The thread never started */
Curl_mutex_release(&addr_ctx->mutx);
err = errno;
goto err_exit;
}
Curl_mutex_release(&addr_ctx->mutx);
else {
#ifdef USE_CURL_COND_T
/* need to handshake with thread for participation in ref counting */
Curl_cond_wait(&addr_ctx->cond, &addr_ctx->mutx);
DEBUGASSERT(addr_ctx->ref_count >= 1);
#endif
Curl_mutex_release(&addr_ctx->mutx);
}
#ifdef USE_HTTPSRR_ARES
if(async_rr_start(data))
@ -464,6 +523,26 @@ err_exit:
return FALSE;
}
static void async_thrdd_shutdown(struct Curl_easy *data)
{
struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
struct async_thrdd_addr_ctx *addr_ctx = thrdd->addr;
bool done;
if(!addr_ctx)
return;
if(addr_ctx->thread_hnd == curl_thread_t_null)
return;
Curl_mutex_acquire(&addr_ctx->mutx);
done = (addr_ctx->ref_count <= 1);
Curl_mutex_release(&addr_ctx->mutx);
if(!done) {
CURL_TRC_DNS(data, "attempt to cancel resolve thread");
(void)Curl_thread_cancel(&addr_ctx->thread_hnd);
}
}
/*
* 'entry' may be NULL and then no data is returned
*/
@ -473,47 +552,49 @@ static CURLcode asyn_thrdd_await(struct Curl_easy *data,
{
CURLcode result = CURLE_OK;
DEBUGASSERT(addr_ctx->thread_hnd != curl_thread_t_null);
if(addr_ctx->thread_hnd != curl_thread_t_null) {
/* not interested in result? cancel, if still running... */
if(!entry)
async_thrdd_shutdown(data);
CURL_TRC_DNS(data, "resolve, wait for thread to finish");
/* wait for the thread to resolve the name */
if(Curl_thread_join(&addr_ctx->thread_hnd)) {
if(entry)
result = Curl_async_is_resolved(data, entry);
CURL_TRC_DNS(data, "resolve, wait for thread to finish");
if(Curl_thread_join(&addr_ctx->thread_hnd)) {
#ifdef DEBUGBUILD
Curl_mutex_acquire(&addr_ctx->mutx);
DEBUGASSERT(addr_ctx->ref_count == 1);
Curl_mutex_release(&addr_ctx->mutx);
#endif
if(entry)
result = Curl_async_is_resolved(data, entry);
}
else
DEBUGASSERT(0);
}
else
DEBUGASSERT(0);
data->state.async.done = TRUE;
if(entry)
*entry = data->state.async.dns;
async_thrdd_destroy(data);
return result;
}
/*
* Until we gain a way to signal the resolver threads to stop early, we must
* simply wait for them and ignore their results.
*/
void Curl_async_thrdd_shutdown(struct Curl_easy *data)
{
struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
/* If we are still resolving, we must wait for the threads to fully clean up,
unfortunately. Otherwise, we can simply cancel to clean up any resolver
data. */
if(thrdd->addr && (thrdd->addr->thread_hnd != curl_thread_t_null) &&
!data->set.quick_exit)
(void)asyn_thrdd_await(data, thrdd->addr, NULL);
else
async_thrdd_destroy(data);
async_thrdd_shutdown(data);
}
void Curl_async_thrdd_destroy(struct Curl_easy *data)
{
Curl_async_thrdd_shutdown(data);
struct async_thrdd_ctx *thrdd = &data->state.async.thrdd;
if(thrdd->addr && !data->set.quick_exit) {
(void)asyn_thrdd_await(data, thrdd->addr, NULL);
}
async_thrdd_destroy(data);
}
/*
@ -612,7 +693,7 @@ CURLcode Curl_async_is_resolved(struct Curl_easy *data,
*dns = data->state.async.dns;
CURL_TRC_DNS(data, "is_resolved() result=%d, dns=%sfound",
result, *dns ? "" : "not ");
async_thrdd_destroy(data);
async_thrdd_shutdown(data);
return result;
}
else {
@ -660,7 +741,9 @@ CURLcode Curl_async_pollset(struct Curl_easy *data, struct easy_pollset *ps)
#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->addr->sock_pair[0] != CURL_SOCKET_BAD) {
result = Curl_pollset_add_in(data, ps, thrdd->addr->sock_pair[0]);
}
#else
{
timediff_t milli;

View File

@ -180,6 +180,9 @@ struct async_thrdd_addr_ctx {
char *hostname; /* hostname to resolve, Curl_async.hostname
duplicate */
curl_mutex_t mutx;
#ifdef USE_CURL_COND_T
curl_cond_t cond;
#endif
#ifndef CURL_DISABLE_SOCKETPAIR
curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */
#endif

View File

@ -100,6 +100,34 @@ int Curl_thread_join(curl_thread_t *hnd)
return ret;
}
/* do not use pthread_cancel if:
* - pthread_cancel seems to be absent
* - on FreeBSD, as we see hangers in CI testing
* - this is a -fsanitize=thread build
* (clang sanitizer reports false positive when functions to not return)
*/
#if defined(PTHREAD_CANCEL_ENABLE) && !defined(__FreeBSD__)
#if defined(__has_feature)
# if !__has_feature(thread_sanitizer)
#define USE_PTHREAD_CANCEL
# endif
#else /* __has_feature */
#define USE_PTHREAD_CANCEL
#endif /* !__has_feature */
#endif /* PTHREAD_CANCEL_ENABLE && !__FreeBSD__ */
int Curl_thread_cancel(curl_thread_t *hnd)
{
(void)hnd;
if(*hnd != curl_thread_t_null)
#ifdef USE_PTHREAD_CANCEL
return pthread_cancel(**hnd);
#else
return 1; /* not supported */
#endif
return 0;
}
#elif defined(USE_THREADS_WIN32)
curl_thread_t Curl_thread_create(CURL_THREAD_RETURN_T
@ -150,7 +178,16 @@ int Curl_thread_join(curl_thread_t *hnd)
Curl_thread_destroy(hnd);
return ret;
}
int Curl_thread_cancel(curl_thread_t *hnd)
{
if(*hnd != curl_thread_t_null) {
return 1; /* not supported */
}
return 0;
}
#endif /* USE_THREADS_* */

View File

@ -34,6 +34,12 @@
# define Curl_mutex_acquire(m) pthread_mutex_lock(m)
# define Curl_mutex_release(m) pthread_mutex_unlock(m)
# define Curl_mutex_destroy(m) pthread_mutex_destroy(m)
# define USE_CURL_COND_T
# define curl_cond_t pthread_cond_t
# define Curl_cond_init(c) pthread_cond_init(c, NULL)
# define Curl_cond_destroy(c) pthread_cond_destroy(c)
# define Curl_cond_wait(c, m) pthread_cond_wait(c, m)
# define Curl_cond_signal(c) pthread_cond_signal(c)
#elif defined(USE_THREADS_WIN32)
# define CURL_STDCALL __stdcall
# define curl_mutex_t CRITICAL_SECTION
@ -47,6 +53,14 @@
# define Curl_mutex_acquire(m) EnterCriticalSection(m)
# define Curl_mutex_release(m) LeaveCriticalSection(m)
# define Curl_mutex_destroy(m) DeleteCriticalSection(m)
# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
# define USE_CURL_COND_T
# define curl_cond_t CONDITION_VARIABLE
# define Curl_cond_init(c) InitializeConditionVariable(c)
# define Curl_cond_destroy(c) (void)(c)
# define Curl_cond_wait(c, m) SleepConditionVariableCS(c, m, INFINITE)
# define Curl_cond_signal(c) WakeConditionVariable(c)
# endif
#else
# define CURL_STDCALL
#endif
@ -66,6 +80,22 @@ void Curl_thread_destroy(curl_thread_t *hnd);
int Curl_thread_join(curl_thread_t *hnd);
int Curl_thread_cancel(curl_thread_t *hnd);
#if defined(USE_THREADS_POSIX) && defined(PTHREAD_CANCEL_ENABLE)
#define Curl_thread_push_cleanup(a,b) pthread_cleanup_push(a,b)
#define Curl_thread_pop_cleanup() pthread_cleanup_pop(0)
#define Curl_thread_enable_cancel() \
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL)
#define Curl_thread_disable_cancel() \
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL)
#else
#define Curl_thread_push_cleanup(a,b) ((void)a,(void)b)
#define Curl_thread_pop_cleanup() Curl_nop_stmt
#define Curl_thread_enable_cancel() Curl_nop_stmt
#define Curl_thread_disable_cancel() Curl_nop_stmt
#endif
#endif /* USE_THREADS_POSIX || USE_THREADS_WIN32 */
#endif /* HEADER_CURL_THREADS_H */

View File

@ -1132,6 +1132,10 @@ CURLcode Curl_resolv_timeout(struct Curl_easy *data,
prev_alarm = alarm(curlx_sltoui(timeout/1000L));
}
#ifdef DEBUGBUILD
Curl_resolve_test_delay();
#endif
#else /* USE_ALARM_TIMEOUT */
#ifndef CURLRES_ASYNCH
if(timeoutms)
@ -1634,3 +1638,18 @@ CURLcode Curl_resolver_error(struct Curl_easy *data)
return result;
}
#endif /* USE_CURL_ASYNC */
#ifdef DEBUGBUILD
#include "curlx/wait.h"
void Curl_resolve_test_delay(void)
{
const char *p = getenv("CURL_DNS_DELAY_MS");
if(p) {
curl_off_t l;
if(!curlx_str_number(&p, &l, TIME_T_MAX) && l) {
curlx_wait_ms((timediff_t)l);
}
}
}
#endif

View File

@ -216,4 +216,8 @@ struct Curl_addrinfo *Curl_sync_getaddrinfo(struct Curl_easy *data,
#endif
#ifdef DEBUGBUILD
void Curl_resolve_test_delay(void);
#endif
#endif /* HEADER_CURL_HOSTIP_H */

View File

@ -51,6 +51,7 @@ my %wl = (
'Curl_creader_def_read' => 'internal api',
'Curl_creader_def_total_length' => 'internal api',
'Curl_meta_reset' => 'internal api',
'Curl_thread_destroy' => 'internal api',
'Curl_trc_dns' => 'internal api',
'curlx_base64_decode' => 'internal api',
'curlx_base64_encode' => 'internal api',

View File

@ -110,7 +110,7 @@ test736 test737 test738 test739 test740 test741 test742 test743 test744 \
test745 test746 test747 test748 test749 test750 test751 test752 test753 \
test754 test755 test756 test757 test758 \
test780 test781 test782 test783 test784 test785 test786 test787 test788 \
test789 test790 test791 test792 test793 test794 test796 test797 \
test789 test790 test791 test792 test793 test794 test795 test796 test797 \
\
test799 test800 test801 test802 test803 test804 test805 test806 test807 \
test808 test809 test810 test811 test812 test813 test814 test815 test816 \

39
tests/data/test795 Normal file
View File

@ -0,0 +1,39 @@
<testcase>
<info>
<keywords>
DNS
</keywords>
</info>
# Client-side
<client>
<features>
http
Debug
!c-ares
!win32
</features>
<name>
Delayed resolve --connect-timeout check
</name>
<server>
none
</server>
<setenv>
CURL_DNS_DELAY_MS=5000
</setenv>
<command>
http://test.invalid -v --no-progress-meter --trace-config dns --connect-timeout 1 -w \%{time_total}
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<errorcode>
28
</errorcode>
<postcheck>
%SRCDIR/libtest/test795.pl %LOGDIR/stdout%TESTNUMBER 2 >> %LOGDIR/stderr%TESTNUMBER
</postcheck>
</verify>
</testcase>

View File

@ -42,7 +42,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/include \
include Makefile.inc
EXTRA_DIST = CMakeLists.txt $(FIRST_C) $(FIRST_H) $(UTILS_C) $(UTILS_H) $(TESTS_C) \
test307.pl test610.pl test613.pl test1013.pl test1022.pl mk-lib1521.pl
test307.pl test610.pl test613.pl test795.pl test1013.pl test1022.pl mk-lib1521.pl
CFLAGS += @CURL_CFLAG_EXTRAS@

46
tests/libtest/test795.pl Executable file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env perl
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) 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.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
#
###########################################################################
use strict;
use warnings;
my $ok = 1;
my $exp_duration = $ARGV[1] + 0.0;
# Read the output of curl --version
open(F, $ARGV[0]) || die "Can't open test result from $ARGV[0]\n";
$_ = <F>;
chomp;
/\s*([\.\d]+)\s*/;
my $duration = $1 + 0.0;
close F;
if ($duration <= $exp_duration) {
print "OK: duration of $duration in expected range\n";
$ok = 0;
}
else {
print "FAILED: duration of $duration is larger than $exp_duration\n";
}
exit $ok;