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);
+ 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;
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;