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;