]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
threaded-resolver: fix shutdown
authorStefan Eissing <stefan@eissing.org>
Wed, 20 Aug 2025 13:48:20 +0000 (15:48 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 21 Aug 2025 07:26:49 +0000 (09:26 +0200)
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

12 files changed:
docs/libcurl/libcurl-env-dbg.md
lib/asyn-thrdd.c
lib/asyn.h
lib/curl_threads.c
lib/curl_threads.h
lib/hostip.c
lib/hostip.h
scripts/singleuse.pl
tests/data/Makefile.am
tests/data/test795 [new file with mode: 0644]
tests/libtest/Makefile.am
tests/libtest/test795.pl [new file with mode: 0755]

index 3fcc1935d5ee3a92959f9574c12c539942b968b7..d142e94410f4c270a720311340c68246c04a2aa7 100644 (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
index 1c0931f4a274e80e3fd8cf36d002d722c80f529c..c0385549ad1bf9a033339d9d227e5f7929394fd7 100644 (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);
-    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;
index de7cd8406bbbc60ed8ce5827a0c545da415216de..5e56532bf09323c0bdb5344368cd5b577838d07e 100644 (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
index bb2e026cbeb47db8e78b53ac0970f905df59072d..fe41c8bab5028696b7827e9a2a2375fb7a30e063 100644 (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_* */
index 82f08c5fbb566d8cb6349c06bd0b959047545f5c..ea25acf750611c9f9322d0e00cd03c5157b0b0b9 100644 (file)
 #  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
 #  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 */
index 74ed5c027783cd0a7338876335e822300607481c..e36184b4e3cbda08cf997638984fb2dd5b29f1cf 100644 (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
index 633157782b03882cdfb1d1106a19b3604f49112e..65fa78fa5001789584c4c64e59fe8458530b71e2 100644 (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 */
index 8240d46e5e1feed88b5b9cc45c9854082a57124a..755c3987fe9c43dd22e0be0ded8bbc0cf759e97a 100755 (executable)
@@ -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',
index 71fc94860d4a416223d90448346a2081ab462d41..3879c370e587b01478a6967ee835dd992bc6f58e 100644 (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 \
diff --git a/tests/data/test795 b/tests/data/test795
new file mode 100644 (file)
index 0000000..5eeb5b7
--- /dev/null
@@ -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>
index b62a359eabefbeceb28d73174c274e3f1c11e80e..4ccfbd2b7bae3b637eab9d6bfe6498bfb52f094f 100644 (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@
 
diff --git a/tests/libtest/test795.pl b/tests/libtest/test795.pl
new file mode 100755 (executable)
index 0000000..6aa793f
--- /dev/null
@@ -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;