From 88fc6c491f043ed184ea2cf1a17b651427fbbbf5 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Wed, 20 Aug 2025 15:48:20 +0200 Subject: [PATCH] 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 --- docs/libcurl/libcurl-env-dbg.md | 5 + lib/asyn-thrdd.c | 325 ++++++++++++++++++++------------ lib/asyn.h | 3 + lib/curl_threads.c | 37 ++++ lib/curl_threads.h | 30 +++ lib/hostip.c | 19 ++ lib/hostip.h | 4 + scripts/singleuse.pl | 1 + tests/data/Makefile.am | 2 +- tests/data/test795 | 39 ++++ tests/libtest/Makefile.am | 2 +- tests/libtest/test795.pl | 46 +++++ 12 files changed, 390 insertions(+), 123 deletions(-) create mode 100644 tests/data/test795 create mode 100755 tests/libtest/test795.pl diff --git a/docs/libcurl/libcurl-env-dbg.md b/docs/libcurl/libcurl-env-dbg.md index 3fcc1935d5..d142e94410 100644 --- a/docs/libcurl/libcurl-env-dbg.md +++ b/docs/libcurl/libcurl-env-dbg.md @@ -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 diff --git a/lib/asyn-thrdd.c b/lib/asyn-thrdd.c index 1c0931f4a2..c0385549ad 100644 --- a/lib/asyn-thrdd.c +++ b/lib/asyn-thrdd.c @@ -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); - Curl_mutex_destroy(&addr_ctx->mutx); - free(addr_ctx->hostname); - if(addr_ctx->res) - Curl_freeaddrinfo(addr_ctx->res); + 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 - /* - * 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. - */ + 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]); + 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); 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; + +/* 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(); - rc = Curl_getaddrinfo_ex(addr_ctx->hostname, service, - &addr_ctx->hints, &addr_ctx->res); +#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 }; -#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; - } - } + Curl_thread_pop_cleanup(); +#if defined(__clang__) +#pragma clang diagnostic pop #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; diff --git a/lib/asyn.h b/lib/asyn.h index de7cd8406b..5e56532bf0 100644 --- a/lib/asyn.h +++ b/lib/asyn.h @@ -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 diff --git a/lib/curl_threads.c b/lib/curl_threads.c index bb2e026cbe..fe41c8bab5 100644 --- a/lib/curl_threads.c +++ b/lib/curl_threads.c @@ -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_* */ diff --git a/lib/curl_threads.h b/lib/curl_threads.h index 82f08c5fbb..ea25acf750 100644 --- a/lib/curl_threads.h +++ b/lib/curl_threads.h @@ -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 */ diff --git a/lib/hostip.c b/lib/hostip.c index 74ed5c0277..e36184b4e3 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -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 diff --git a/lib/hostip.h b/lib/hostip.h index 633157782b..65fa78fa50 100644 --- a/lib/hostip.h +++ b/lib/hostip.h @@ -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 */ diff --git a/scripts/singleuse.pl b/scripts/singleuse.pl index 8240d46e5e..755c3987fe 100755 --- a/scripts/singleuse.pl +++ b/scripts/singleuse.pl @@ -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', diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 71fc94860d..3879c370e5 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -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 \ diff --git a/tests/data/test795 b/tests/data/test795 new file mode 100644 index 0000000000..5eeb5b7d83 --- /dev/null +++ b/tests/data/test795 @@ -0,0 +1,39 @@ + + + +DNS + + + +# Client-side + + +http +Debug +!c-ares +!win32 + + +Delayed resolve --connect-timeout check + + +none + + +CURL_DNS_DELAY_MS=5000 + + +http://test.invalid -v --no-progress-meter --trace-config dns --connect-timeout 1 -w \%{time_total} + + + +# Verify data after the test has been "shot" + + +28 + + +%SRCDIR/libtest/test795.pl %LOGDIR/stdout%TESTNUMBER 2 >> %LOGDIR/stderr%TESTNUMBER + + + diff --git a/tests/libtest/Makefile.am b/tests/libtest/Makefile.am index b62a359eab..4ccfbd2b7b 100644 --- a/tests/libtest/Makefile.am +++ b/tests/libtest/Makefile.am @@ -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@ diff --git a/tests/libtest/test795.pl b/tests/libtest/test795.pl new file mode 100755 index 0000000000..6aa793f7f6 --- /dev/null +++ b/tests/libtest/test795.pl @@ -0,0 +1,46 @@ +#!/usr/bin/env perl +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , 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"; +$_ = ; +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; -- 2.47.3