]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
shutdowns: split shutdown handling from connection pool
authorStefan Eissing <stefan@eissing.org>
Thu, 27 Feb 2025 14:47:30 +0000 (15:47 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Sun, 2 Mar 2025 10:13:34 +0000 (11:13 +0100)
Further testing with timeouts in event based processing revealed that
our current shutdown handling in the connection pool was not clear
enough. Graceful shutdowns can only happen inside a multi handle and it
was confusing to track in the code which situation actually applies. It
seems better to split the shutdown handling off and have that code
always be part of a multi handle.

Add `cshutdn.[ch]` with its own struct to maintain connections being
shut down. A `cshutdn` always belongs to a multi handle and uses that
for socket/timeout monitoring.

The `cpool`, which can be part of a multi or share, either passes
connections to a `cshutdn` or terminates them with a one-time, best
effort.

Add an `admin` easy handle to each multi and share. This is used to
perform all maintenance operations where no "real" easy handle is
available. This solves the problem that the multi admin handle requires
some additional initialisation (e.g. timeout list).

The share needs its admin handle as it is often cleaned up when no other
transfer or multi handle exists any more. But we need a `data` in almost
every call.

Fix file:// handling of errors when adding a new connection to the pool.

Changes in `curl` itself:

- for parallel transfers, do not set a connection pool in the share,
  rely on the multi's connection pool instead. While not a requirement
  for the new `cshutdn` to work, this is

  a) helpful in testing to trigger graceful shutdowns
  b) a broader code coverage of libcurl via the curl tool

- on test_event with uv, cleanup the multi handle before returning from
  parallel_event(). The uv struct is on the stack, cleanup of the multi
  later will crash when it tries to register sockets. This is a "eat
  your own dogfood" related fix.

Closes #16508

23 files changed:
lib/Makefile.inc
lib/cfilters.c
lib/conncache.c
lib/conncache.h
lib/connect.c
lib/connect.h
lib/cshutdn.c [new file with mode: 0644]
lib/cshutdn.h [new file with mode: 0644]
lib/easy.c
lib/ftp.c
lib/ftp.h
lib/hostip.c
lib/multi.c
lib/multihandle.h
lib/pingpong.c
lib/share.c
lib/share.h
lib/url.c
lib/urldata.h
lib/vtls/vtls.c
src/tool_operate.c
tests/data/test1554
tests/http/test_19_shutdown.py

index 543b4d4ca67e301e57b99dbbfa587060f58be633..58ce220e7b27c3371095d72fdb2aa5eb64c43770 100644 (file)
@@ -123,6 +123,7 @@ LIB_CFILES =         \
   cf-socket.c        \
   cfilters.c         \
   conncache.c        \
+  cshutdn.c          \
   connect.c          \
   content_encoding.c \
   cookie.c           \
@@ -260,6 +261,7 @@ LIB_HFILES =         \
   cf-socket.h        \
   cfilters.h         \
   conncache.h        \
+  cshutdn.h          \
   connect.h          \
   content_encoding.h \
   cookie.h           \
index c7f9ab71f81c75abb29d68af32453d840d4bc059..3521ed6ed240120bc957ed11f9907a0bc1d2f85e 100644 (file)
@@ -202,7 +202,7 @@ CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done)
   if(!Curl_shutdown_started(data, sockindex)) {
     CURL_TRC_M(data, "shutdown start on%s connection",
                sockindex ? " secondary" : "");
-    Curl_shutdown_start(data, sockindex, &now);
+    Curl_shutdown_start(data, sockindex, 0, &now);
   }
   else {
     timeout_ms = Curl_shutdown_timeleft(data->conn, sockindex, &now);
index d926eb69e375c1be9d4cb58a818f23bbb981149a..137784918148c86c1a656295e324c5bd4d89e452 100644 (file)
@@ -34,6 +34,7 @@
 #include "multiif.h"
 #include "multi_ev.h"
 #include "sendf.h"
+#include "cshutdn.h"
 #include "conncache.h"
 #include "http_negotiate.h"
 #include "http_ntlm.h"
 
 #define CPOOL_IS_LOCKED(c)    ((c) && (c)->locked)
 
-#define CPOOL_LOCK(c)                                                   \
+#define CPOOL_LOCK(c,d)                                                 \
   do {                                                                  \
     if((c)) {                                                           \
       if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
-        Curl_share_lock(((c)->idata), CURL_LOCK_DATA_CONNECT,           \
+        Curl_share_lock((d), CURL_LOCK_DATA_CONNECT,                    \
                         CURL_LOCK_ACCESS_SINGLE);                       \
       DEBUGASSERT(!(c)->locked);                                        \
       (c)->locked = TRUE;                                               \
     }                                                                   \
   } while(0)
 
-#define CPOOL_UNLOCK(c)                                                 \
+#define CPOOL_UNLOCK(c,d)                                                 \
   do {                                                                  \
     if((c)) {                                                           \
       DEBUGASSERT((c)->locked);                                         \
       (c)->locked = FALSE;                                              \
       if(CURL_SHARE_KEEP_CONNECT((c)->share))                           \
-        Curl_share_unlock((c)->idata, CURL_LOCK_DATA_CONNECT);          \
+        Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT);                 \
     }                                                                   \
   } while(0)
 
@@ -86,24 +87,6 @@ static void cpool_discard_conn(struct cpool *cpool,
                                struct Curl_easy *data,
                                struct connectdata *conn,
                                bool aborted);
-static void cpool_close_and_destroy(struct cpool *cpool,
-                                    struct connectdata *conn,
-                                    struct Curl_easy *data,
-                                    bool do_shutdown);
-static void cpool_run_conn_shutdown(struct Curl_easy *data,
-                                    struct connectdata *conn,
-                                    bool *done);
-static void cpool_run_conn_shutdown_handler(struct Curl_easy *data,
-                                            struct connectdata *conn);
-static CURLMcode cpool_update_shutdown_ev(struct cpool *cpool,
-                                          struct Curl_multi *multi,
-                                          struct connectdata *conn);
-static void cpool_shutdown_all(struct cpool *cpool,
-                               struct Curl_easy *data, int timeout_ms);
-static void cpool_close_and_destroy_all(struct cpool *cpool);
-static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool);
-static size_t cpool_shutdown_dest_count(struct cpool *cpool,
-                                        const char *destination);
 
 static struct cpool_bundle *cpool_bundle_create(const char *dest,
                                                 size_t dest_len)
@@ -150,55 +133,106 @@ static void cpool_bundle_free_entry(void *freethis)
 
 int Curl_cpool_init(struct cpool *cpool,
                     Curl_cpool_disconnect_cb *disconnect_cb,
-                    struct Curl_multi *multi,
+                    struct Curl_easy *idata,
                     struct Curl_share *share,
                     size_t size)
 {
-  DEBUGASSERT(!!multi != !!share); /* either one */
   Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str,
                  Curl_str_key_compare, cpool_bundle_free_entry);
-  Curl_llist_init(&cpool->shutdowns, NULL);
 
+  DEBUGASSERT(idata);
   DEBUGASSERT(disconnect_cb);
   if(!disconnect_cb)
     return 1;
 
-  /* allocate a new easy handle to use when closing cached connections */
-  cpool->idata = curl_easy_init();
-  if(!cpool->idata)
-    return 1; /* bad */
-  cpool->idata->state.internal = TRUE;
-  /* This is quirky. We need an internal handle for certain operations, but we
-   * do not add it to the multi (if there is one). We give it the multi so
-   * that socket event operations can work. Probably better to have an
-   * internal handle owned by the multi that can be used for cpool
-   * operations. */
-  cpool->idata->multi = multi;
-#ifdef DEBUGBUILD
-  if(getenv("CURL_DEBUG"))
-    cpool->idata->set.verbose = TRUE;
-#endif
-
+  cpool->idata = idata;
   cpool->disconnect_cb = disconnect_cb;
-  cpool->idata->multi = cpool->multi = multi;
-  cpool->idata->share = cpool->share = share;
-
+  cpool->share = share;
+  cpool->initialised = TRUE;
   return 0; /* good */
 }
 
+/* Return the "first" connection in the pool or NULL. */
+static struct connectdata *cpool_get_first(struct cpool *cpool)
+{
+  struct Curl_hash_iterator iter;
+  struct Curl_hash_element *he;
+  struct cpool_bundle *bundle;
+  struct Curl_llist_node *conn_node;
+
+  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
+  for(he = Curl_hash_next_element(&iter); he;
+      he = Curl_hash_next_element(&iter)) {
+    bundle = he->ptr;
+    conn_node = Curl_llist_head(&bundle->conns);
+    if(conn_node)
+      return Curl_node_elem(conn_node);
+  }
+  return NULL;
+}
+
+
+static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
+                                              struct connectdata *conn)
+{
+  return Curl_hash_pick(&cpool->dest2bundle,
+                        conn->destination, conn->destination_len);
+}
+
+
+static void cpool_remove_bundle(struct cpool *cpool,
+                                struct cpool_bundle *bundle)
+{
+  if(!cpool)
+    return;
+  Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len);
+}
+
+
+static void cpool_remove_conn(struct cpool *cpool,
+                              struct connectdata *conn)
+{
+  struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
+  DEBUGASSERT(cpool);
+  if(list) {
+    /* The connection is certainly in the pool, but where? */
+    struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn);
+    if(bundle && (list == &bundle->conns)) {
+      cpool_bundle_remove(bundle, conn);
+      if(!Curl_llist_count(&bundle->conns))
+        cpool_remove_bundle(cpool, bundle);
+      conn->bits.in_cpool = FALSE;
+      cpool->num_conn--;
+    }
+    else {
+      /* Should have been in the bundle list */
+      DEBUGASSERT(NULL);
+    }
+  }
+}
+
 void Curl_cpool_destroy(struct cpool *cpool)
 {
-  if(cpool) {
-    if(cpool->idata) {
-      cpool_close_and_destroy_all(cpool);
-      /* The internal closure handle is special and we need to
-       * disconnect it from multi/share before closing it down. */
-      cpool->idata->multi = NULL;
-      cpool->idata->share = NULL;
-      Curl_close(&cpool->idata);
+  if(cpool && cpool->initialised && cpool->idata) {
+    struct connectdata *conn;
+    SIGPIPE_VARIABLE(pipe_st);
+
+    CURL_TRC_M(cpool->idata, "%s[CPOOL] destroy, %zu connections",
+               cpool->share ? "[SHARE] " : "", cpool->num_conn);
+    /* Move all connections to the shutdown list */
+    sigpipe_init(&pipe_st);
+    CPOOL_LOCK(cpool, cpool->idata);
+    conn = cpool_get_first(cpool);
+    while(conn) {
+      cpool_remove_conn(cpool, conn);
+      sigpipe_apply(cpool->idata, &pipe_st);
+      connclose(conn, "kill all");
+      cpool_discard_conn(cpool, cpool->idata, conn, FALSE);
+      conn = cpool_get_first(cpool);
     }
+    CPOOL_UNLOCK(cpool, cpool->idata);
+    sigpipe_restore(&pipe_st);
     Curl_hash_destroy(&cpool->dest2bundle);
-    cpool->multi = NULL;
   }
 }
 
@@ -221,23 +255,14 @@ void Curl_cpool_xfer_init(struct Curl_easy *data)
 
   DEBUGASSERT(cpool);
   if(cpool) {
-    CPOOL_LOCK(cpool);
+    CPOOL_LOCK(cpool, data);
     /* the identifier inside the connection cache */
     data->id = cpool->next_easy_id++;
     if(cpool->next_easy_id <= 0)
       cpool->next_easy_id = 0;
     data->state.lastconnect_id = -1;
 
-    /* The closure handle only ever has default timeouts set. To improve the
-       state somewhat we clone the timeouts from each added handle so that the
-       closure handle always has the same timeouts as the most recently added
-       easy handle. */
-    cpool->idata->set.timeout = data->set.timeout;
-    cpool->idata->set.server_response_timeout =
-      data->set.server_response_timeout;
-    cpool->idata->set.no_signal = data->set.no_signal;
-
-    CPOOL_UNLOCK(cpool);
+    CPOOL_UNLOCK(cpool, data);
   }
   else {
     /* We should not get here, but in a non-debug build, do something */
@@ -246,13 +271,6 @@ void Curl_cpool_xfer_init(struct Curl_easy *data)
   }
 }
 
-static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
-                                              struct connectdata *conn)
-{
-  return Curl_hash_pick(&cpool->dest2bundle,
-                        conn->destination, conn->destination_len);
-}
-
 static struct cpool_bundle *
 cpool_add_bundle(struct cpool *cpool, struct connectdata *conn)
 {
@@ -270,17 +288,70 @@ cpool_add_bundle(struct cpool *cpool, struct connectdata *conn)
   return bundle;
 }
 
-static void cpool_remove_bundle(struct cpool *cpool,
-                                struct cpool_bundle *bundle)
+static struct connectdata *
+cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle)
 {
-  if(!cpool)
-    return;
+  struct Curl_llist_node *curr;
+  timediff_t highscore = -1;
+  timediff_t score;
+  struct curltime now;
+  struct connectdata *oldest_idle = NULL;
+  struct connectdata *conn;
 
-  Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len);
+  now = Curl_now();
+  curr = Curl_llist_head(&bundle->conns);
+  while(curr) {
+    conn = Curl_node_elem(curr);
+
+    if(!CONN_INUSE(conn)) {
+      /* Set higher score for the age passed since the connection was used */
+      score = Curl_timediff(now, conn->lastused);
+
+      if(score > highscore) {
+        highscore = score;
+        oldest_idle = conn;
+      }
+    }
+    curr = Curl_node_next(curr);
+  }
+  return oldest_idle;
+}
+
+static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool)
+{
+  struct Curl_hash_iterator iter;
+  struct Curl_llist_node *curr;
+  struct Curl_hash_element *he;
+  struct connectdata *oldest_idle = NULL;
+  struct cpool_bundle *bundle;
+  struct curltime now;
+  timediff_t highscore =- 1;
+  timediff_t score;
+
+  now = Curl_now();
+  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
+
+  for(he = Curl_hash_next_element(&iter); he;
+      he = Curl_hash_next_element(&iter)) {
+    struct connectdata *conn;
+    bundle = he->ptr;
+
+    for(curr = Curl_llist_head(&bundle->conns); curr;
+        curr = Curl_node_next(curr)) {
+      conn = Curl_node_elem(curr);
+      if(CONN_INUSE(conn) || conn->bits.close || conn->connect_only)
+        continue;
+      /* Set higher score for the age passed since the connection was used */
+      score = Curl_timediff(now, conn->lastused);
+      if(score > highscore) {
+        highscore = score;
+        oldest_idle = conn;
+      }
+    }
+  }
+  return oldest_idle;
 }
 
-static struct connectdata *
-cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle);
 
 int Curl_cpool_check_limits(struct Curl_easy *data,
                             struct connectdata *conn)
@@ -295,21 +366,21 @@ int Curl_cpool_check_limits(struct Curl_easy *data,
   if(!cpool)
     return CPOOL_LIMIT_OK;
 
-  if(data && data->multi) {
-    dest_limit = data->multi->max_host_connections;
-    total_limit = data->multi->max_total_connections;
+  if(cpool->idata->multi) {
+    dest_limit = cpool->idata->multi->max_host_connections;
+    total_limit = cpool->idata->multi->max_total_connections;
   }
 
   if(!dest_limit && !total_limit)
     return CPOOL_LIMIT_OK;
 
-  CPOOL_LOCK(cpool);
+  CPOOL_LOCK(cpool, cpool->idata);
   if(dest_limit) {
     size_t live;
 
     bundle = cpool_find_bundle(cpool, conn);
     live = bundle ? Curl_llist_count(&bundle->conns) : 0;
-    shutdowns = cpool_shutdown_dest_count(cpool, conn->destination);
+      shutdowns = Curl_cshutdn_dest_count(data, conn->destination);
     while(!shutdowns && bundle && live >= dest_limit) {
       struct connectdata *oldest_idle = NULL;
       /* The bundle is full. Extract the oldest connection that may
@@ -322,12 +393,12 @@ int Curl_cpool_check_limits(struct Curl_easy *data,
                    FMT_OFF_T " from %zu to reach destination "
                    "limit of %zu", oldest_idle->connection_id,
                    Curl_llist_count(&bundle->conns), dest_limit);
-      Curl_cpool_disconnect(data, oldest_idle, FALSE);
+      Curl_conn_terminate(cpool->idata, oldest_idle, FALSE);
 
       /* in case the bundle was destroyed in disconnect, look it up again */
       bundle = cpool_find_bundle(cpool, conn);
       live = bundle ? Curl_llist_count(&bundle->conns) : 0;
-      shutdowns = cpool_shutdown_dest_count(cpool, conn->destination);
+      shutdowns = Curl_cshutdn_dest_count(cpool->idata, conn->destination);
     }
     if((live + shutdowns) >= dest_limit) {
       result = CPOOL_LIMIT_DEST;
@@ -336,7 +407,7 @@ int Curl_cpool_check_limits(struct Curl_easy *data,
   }
 
   if(total_limit) {
-    shutdowns = Curl_llist_count(&cpool->shutdowns);
+    shutdowns = Curl_cshutdn_count(cpool->idata);
     while((cpool->num_conn + shutdowns) >= total_limit) {
       struct connectdata *oldest_idle = cpool_get_oldest_idle(cpool);
       if(!oldest_idle)
@@ -346,8 +417,8 @@ int Curl_cpool_check_limits(struct Curl_easy *data,
                  FMT_OFF_T " from %zu to reach total "
                  "limit of %zu",
                  oldest_idle->connection_id, cpool->num_conn, total_limit);
-      Curl_cpool_disconnect(data, oldest_idle, FALSE);
-      shutdowns = Curl_llist_count(&cpool->shutdowns);
+      Curl_conn_terminate(cpool->idata, oldest_idle, FALSE);
+      shutdowns = Curl_cshutdn_count(cpool->idata);
     }
     if((cpool->num_conn + shutdowns) >= total_limit) {
       result = CPOOL_LIMIT_TOTAL;
@@ -356,12 +427,12 @@ int Curl_cpool_check_limits(struct Curl_easy *data,
   }
 
 out:
-  CPOOL_UNLOCK(cpool);
+  CPOOL_UNLOCK(cpool, cpool->idata);
   return result;
 }
 
-CURLcode Curl_cpool_add_conn(struct Curl_easy *data,
-                             struct connectdata *conn)
+CURLcode Curl_cpool_add(struct Curl_easy *data,
+                        struct connectdata *conn)
 {
   CURLcode result = CURLE_OK;
   struct cpool_bundle *bundle = NULL;
@@ -372,7 +443,7 @@ CURLcode Curl_cpool_add_conn(struct Curl_easy *data,
   if(!cpool)
     return CURLE_FAILED_INIT;
 
-  CPOOL_LOCK(cpool);
+  CPOOL_LOCK(cpool, data);
   bundle = cpool_find_bundle(cpool, conn);
   if(!bundle) {
     bundle = cpool_add_bundle(cpool, conn);
@@ -387,36 +458,13 @@ CURLcode Curl_cpool_add_conn(struct Curl_easy *data,
   cpool->num_conn++;
   DEBUGF(infof(data, "Added connection %" FMT_OFF_T ". "
                "The cache now contains %zu members",
-               conn->connection_id,
-               cpool->num_conn + Curl_llist_count(&cpool->shutdowns)));
+               conn->connection_id, cpool->num_conn));
 out:
-  CPOOL_UNLOCK(cpool);
+  CPOOL_UNLOCK(cpool, data);
 
   return result;
 }
 
-static void cpool_remove_conn(struct cpool *cpool,
-                              struct connectdata *conn)
-{
-  struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
-  DEBUGASSERT(cpool);
-  if(list) {
-    /* The connection is certainly in the pool, but where? */
-    struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn);
-    if(bundle && (list == &bundle->conns)) {
-      cpool_bundle_remove(bundle, conn);
-      if(!Curl_llist_count(&bundle->conns))
-        cpool_remove_bundle(cpool, bundle);
-      conn->bits.in_cpool = FALSE;
-      cpool->num_conn--;
-    }
-    else {
-      /* Not in a bundle, already in the shutdown list? */
-      DEBUGASSERT(list == &cpool->shutdowns);
-    }
-  }
-}
-
 /* This function iterates the entire connection pool and calls the function
    func() with the connection pointer as the first argument and the supplied
    'param' argument as the other.
@@ -464,25 +512,6 @@ static bool cpool_foreach(struct Curl_easy *data,
   return FALSE;
 }
 
-/* Return a live connection in the pool or NULL. */
-static struct connectdata *cpool_get_live_conn(struct cpool *cpool)
-{
-  struct Curl_hash_iterator iter;
-  struct Curl_hash_element *he;
-  struct cpool_bundle *bundle;
-  struct Curl_llist_node *conn_node;
-
-  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
-  for(he = Curl_hash_next_element(&iter); he;
-      he = Curl_hash_next_element(&iter)) {
-    bundle = he->ptr;
-    conn_node = Curl_llist_head(&bundle->conns);
-    if(conn_node)
-      return Curl_node_elem(conn_node);
-  }
-  return NULL;
-}
-
 /*
  * A connection (already in the pool) has become idle. Do any
  * cleanups in regard to the pool's limits.
@@ -503,91 +532,24 @@ bool Curl_cpool_conn_now_idle(struct Curl_easy *data,
     /* may be called form a callback already under lock */
     bool do_lock = !CPOOL_IS_LOCKED(cpool);
     if(do_lock)
-      CPOOL_LOCK(cpool);
+      CPOOL_LOCK(cpool, data);
     if(cpool->num_conn > maxconnects) {
-      infof(data, "Connection pool is full, closing the oldest one");
+      infof(data, "Connection pool is full, closing the oldest of %zu/%u",
+            cpool->num_conn, maxconnects);
 
       oldest_idle = cpool_get_oldest_idle(cpool);
       kept = (oldest_idle != conn);
       if(oldest_idle) {
-        Curl_cpool_disconnect(cpool->idata, oldest_idle, FALSE);
+        Curl_conn_terminate(data, oldest_idle, FALSE);
       }
     }
     if(do_lock)
-      CPOOL_UNLOCK(cpool);
+      CPOOL_UNLOCK(cpool, data);
   }
 
   return kept;
 }
 
-/*
- * This function finds the connection in the connection bundle that has been
- * unused for the longest time.
- */
-static struct connectdata *
-cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle)
-{
-  struct Curl_llist_node *curr;
-  timediff_t highscore = -1;
-  timediff_t score;
-  struct curltime now;
-  struct connectdata *oldest_idle = NULL;
-  struct connectdata *conn;
-
-  now = Curl_now();
-  curr = Curl_llist_head(&bundle->conns);
-  while(curr) {
-    conn = Curl_node_elem(curr);
-
-    if(!CONN_INUSE(conn)) {
-      /* Set higher score for the age passed since the connection was used */
-      score = Curl_timediff(now, conn->lastused);
-
-      if(score > highscore) {
-        highscore = score;
-        oldest_idle = conn;
-      }
-    }
-    curr = Curl_node_next(curr);
-  }
-  return oldest_idle;
-}
-
-static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool)
-{
-  struct Curl_hash_iterator iter;
-  struct Curl_llist_node *curr;
-  struct Curl_hash_element *he;
-  struct connectdata *oldest_idle = NULL;
-  struct cpool_bundle *bundle;
-  struct curltime now;
-  timediff_t highscore =- 1;
-  timediff_t score;
-
-  now = Curl_now();
-  Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
-
-  for(he = Curl_hash_next_element(&iter); he;
-      he = Curl_hash_next_element(&iter)) {
-    struct connectdata *conn;
-    bundle = he->ptr;
-
-    for(curr = Curl_llist_head(&bundle->conns); curr;
-        curr = Curl_node_next(curr)) {
-      conn = Curl_node_elem(curr);
-      if(CONN_INUSE(conn) || conn->bits.close || conn->connect_only)
-        continue;
-      /* Set higher score for the age passed since the connection was used */
-      score = Curl_timediff(now, conn->lastused);
-      if(score > highscore) {
-        highscore = score;
-        oldest_idle = conn;
-      }
-    }
-  }
-  return oldest_idle;
-}
-
 bool Curl_cpool_find(struct Curl_easy *data,
                      const char *destination, size_t dest_len,
                      Curl_cpool_conn_match_cb *conn_cb,
@@ -603,7 +565,7 @@ bool Curl_cpool_find(struct Curl_easy *data,
   if(!cpool)
     return FALSE;
 
-  CPOOL_LOCK(cpool);
+  CPOOL_LOCK(cpool, data);
   bundle = Curl_hash_pick(&cpool->dest2bundle, (void *)destination, dest_len);
   if(bundle) {
     struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns);
@@ -622,102 +584,10 @@ bool Curl_cpool_find(struct Curl_easy *data,
   if(done_cb) {
     result = done_cb(result, userdata);
   }
-  CPOOL_UNLOCK(cpool);
+  CPOOL_UNLOCK(cpool, data);
   return result;
 }
 
-/* How many connections to the given destination are in shutdown? */
-static size_t cpool_shutdown_dest_count(struct cpool *cpool,
-                                        const char *destination)
-{
-  size_t n = 0;
-  struct Curl_llist_node *e = Curl_llist_head(&cpool->shutdowns);
-  while(e) {
-    struct connectdata *conn = Curl_node_elem(e);
-    if(!strcmp(destination, conn->destination))
-      ++n;
-    e = Curl_node_next(e);
-  }
-  return n;
-}
-
-static void cpool_shutdown_discard_all(struct cpool *cpool)
-{
-  struct Curl_llist_node *e = Curl_llist_head(&cpool->shutdowns);
-  struct connectdata *conn;
-
-  if(!e)
-    return;
-
-  DEBUGF(infof(cpool->idata, "cpool_shutdown_discard_all"));
-  while(e) {
-    conn = Curl_node_elem(e);
-    Curl_node_remove(e);
-    cpool_close_and_destroy(cpool, conn, NULL, FALSE);
-    e = Curl_llist_head(&cpool->shutdowns);
-  }
-}
-
-static void cpool_close_and_destroy_all(struct cpool *cpool)
-{
-  struct connectdata *conn;
-  int timeout_ms = 0;
-  SIGPIPE_VARIABLE(pipe_st);
-
-  DEBUGASSERT(cpool);
-  /* Move all connections to the shutdown list */
-  sigpipe_init(&pipe_st);
-  CPOOL_LOCK(cpool);
-  conn = cpool_get_live_conn(cpool);
-  while(conn) {
-    cpool_remove_conn(cpool, conn);
-    sigpipe_apply(cpool->idata, &pipe_st);
-    connclose(conn, "kill all");
-    cpool_discard_conn(cpool, cpool->idata, conn, FALSE);
-
-    conn = cpool_get_live_conn(cpool);
-  }
-  CPOOL_UNLOCK(cpool);
-
-    /* Just for testing, run graceful shutdown */
-#ifdef DEBUGBUILD
-  {
-    const char *p = getenv("CURL_GRACEFUL_SHUTDOWN");
-    if(p) {
-      curl_off_t l;
-      if(!Curl_str_number(&p, &l, INT_MAX))
-        timeout_ms = (int)l;
-    }
-  }
-#endif
-  sigpipe_apply(cpool->idata, &pipe_st);
-  cpool_shutdown_all(cpool, cpool->idata, timeout_ms);
-
-  /* discard all connections in the shutdown list */
-  cpool_shutdown_discard_all(cpool);
-
-  Curl_hostcache_clean(cpool->idata, cpool->idata->dns.hostcache);
-  sigpipe_restore(&pipe_st);
-}
-
-
-static void cpool_shutdown_destroy_oldest(struct cpool *cpool)
-{
-  struct Curl_llist_node *e;
-  struct connectdata *conn;
-
-  e = Curl_llist_head(&cpool->shutdowns);
-  if(e) {
-    SIGPIPE_VARIABLE(pipe_st);
-    conn = Curl_node_elem(e);
-    Curl_node_remove(e);
-    sigpipe_init(&pipe_st);
-    sigpipe_apply(cpool->idata, &pipe_st);
-    cpool_close_and_destroy(cpool, conn, NULL, FALSE);
-    sigpipe_restore(&pipe_st);
-  }
-}
-
 static void cpool_discard_conn(struct cpool *cpool,
                                struct Curl_easy *data,
                                struct connectdata *conn,
@@ -726,6 +596,7 @@ static void cpool_discard_conn(struct cpool *cpool,
   bool done = FALSE;
 
   DEBUGASSERT(data);
+  DEBUGASSERT(!data->conn);
   DEBUGASSERT(cpool);
   DEBUGASSERT(!conn->bits.in_cpool);
 
@@ -755,47 +626,18 @@ static void cpool_discard_conn(struct cpool *cpool,
     done = TRUE;
   if(!done) {
     /* Attempt to shutdown the connection right away. */
-    Curl_attach_connection(data, conn);
-    cpool_run_conn_shutdown(data, conn, &done);
-    CURL_TRC_M(data, "[CPOOL] shutdown, done=%d", done);
-    Curl_detach_connection(data);
-  }
-
-  if(done) {
-    cpool_close_and_destroy(cpool, conn, data, FALSE);
-    return;
+    Curl_cshutdn_run_once(cpool->idata, conn, &done);
   }
 
-  /* Add the connection to our shutdown list for non-blocking shutdown
-   * during multi processing. */
-  if(data->multi && data->multi->max_total_connections > 0 &&
-     (data->multi->max_total_connections <=
-      (long)(cpool->num_conn + Curl_llist_count(&cpool->shutdowns)))) {
-    CURL_TRC_M(data, "[CPOOL] discarding oldest shutdown connection "
-               "due to connection limit of %ld",
-               data->multi->max_total_connections);
-    cpool_shutdown_destroy_oldest(cpool);
-  }
-
-  if(data->multi && data->multi->socket_cb) {
-    DEBUGASSERT(cpool == &data->multi->cpool);
-    if(cpool_update_shutdown_ev(cpool, data->multi, conn)) {
-      CURL_TRC_M(data, "[CPOOL] update events failed, discarding #%"
-                 FMT_OFF_T, conn->connection_id);
-      cpool_close_and_destroy(cpool, conn, data, FALSE);
-      return;
-    }
-  }
-
-  Curl_llist_append(&cpool->shutdowns, conn, &conn->cpool_node);
-  CURL_TRC_M(data, "[CPOOL] added #%" FMT_OFF_T
-             " to shutdowns, now %zu conns in shutdown",
-             conn->connection_id, Curl_llist_count(&cpool->shutdowns));
+  if(done || !data->multi)
+    Curl_cshutdn_terminate(cpool->idata, conn, FALSE);
+  else
+    Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn);
 }
 
-void Curl_cpool_disconnect(struct Curl_easy *data,
-                           struct connectdata *conn,
-                           bool aborted)
+void Curl_conn_terminate(struct Curl_easy *data,
+                         struct connectdata *conn,
+                         bool aborted)
 {
   struct cpool *cpool = cpool_get_instance(data);
   bool do_lock;
@@ -817,7 +659,7 @@ void Curl_cpool_disconnect(struct Curl_easy *data,
    * user callback in find. */
   do_lock = !CPOOL_IS_LOCKED(cpool);
   if(do_lock)
-    CPOOL_LOCK(cpool);
+    CPOOL_LOCK(cpool, data);
 
   if(conn->bits.in_cpool) {
     cpool_remove_conn(cpool, conn);
@@ -834,413 +676,15 @@ void Curl_cpool_disconnect(struct Curl_easy *data,
     cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
   }
   else {
-    /* No multi available. Make a best-effort shutdown + close */
+    /* No multi available, terminate */
     infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id);
-    cpool_close_and_destroy(NULL, conn, data, !aborted);
+    Curl_cshutdn_terminate(cpool->idata, conn, !aborted);
   }
 
   if(do_lock)
-    CPOOL_UNLOCK(cpool);
+    CPOOL_UNLOCK(cpool, data);
 }
 
-static void cpool_run_conn_shutdown_handler(struct Curl_easy *data,
-                                            struct connectdata *conn)
-{
-  if(!conn->bits.shutdown_handler) {
-    if(conn->dns_entry)
-      Curl_resolv_unlink(data, &conn->dns_entry);
-
-    /* Cleanup NTLM connection-related data */
-    Curl_http_auth_cleanup_ntlm(conn);
-
-    /* Cleanup NEGOTIATE connection-related data */
-    Curl_http_auth_cleanup_negotiate(conn);
-
-    if(conn->handler && conn->handler->disconnect) {
-      /* This is set if protocol-specific cleanups should be made */
-      DEBUGF(infof(data, "connection #%" FMT_OFF_T
-                   ", shutdown protocol handler (aborted=%d)",
-                   conn->connection_id, conn->bits.aborted));
-
-      conn->handler->disconnect(data, conn, conn->bits.aborted);
-    }
-
-    /* possible left-overs from the async name resolvers */
-    Curl_resolver_cancel(data);
-
-    conn->bits.shutdown_handler = TRUE;
-  }
-}
-
-static void cpool_run_conn_shutdown(struct Curl_easy *data,
-                                    struct connectdata *conn,
-                                    bool *done)
-{
-  CURLcode r1, r2;
-  bool done1, done2;
-
-  /* We expect to be attached when called */
-  DEBUGASSERT(data->conn == conn);
-
-  cpool_run_conn_shutdown_handler(data, conn);
-
-  if(conn->bits.shutdown_filters) {
-    *done = TRUE;
-    return;
-  }
-
-  if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET))
-    r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1);
-  else {
-    r1 = CURLE_OK;
-    done1 = TRUE;
-  }
-
-  if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET))
-    r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2);
-  else {
-    r2 = CURLE_OK;
-    done2 = TRUE;
-  }
-
-  /* we are done when any failed or both report success */
-  *done = (r1 || r2 || (done1 && done2));
-  if(*done)
-    conn->bits.shutdown_filters = TRUE;
-}
-
-static CURLcode cpool_add_pollfds(struct cpool *cpool,
-                                  struct curl_pollfds *cpfds)
-{
-  CURLcode result = CURLE_OK;
-
-  if(Curl_llist_head(&cpool->shutdowns)) {
-    struct Curl_llist_node *e;
-    struct easy_pollset ps;
-    struct connectdata *conn;
-
-    for(e = Curl_llist_head(&cpool->shutdowns); e;
-        e = Curl_node_next(e)) {
-      conn = Curl_node_elem(e);
-      memset(&ps, 0, sizeof(ps));
-      Curl_attach_connection(cpool->idata, conn);
-      Curl_conn_adjust_pollset(cpool->idata, conn, &ps);
-      Curl_detach_connection(cpool->idata);
-
-      result = Curl_pollfds_add_ps(cpfds, &ps);
-      if(result) {
-        Curl_pollfds_cleanup(cpfds);
-        goto out;
-      }
-    }
-  }
-out:
-  return result;
-}
-
-CURLcode Curl_cpool_add_pollfds(struct cpool *cpool,
-                                struct curl_pollfds *cpfds)
-{
-  CURLcode result;
-  CPOOL_LOCK(cpool);
-  result = cpool_add_pollfds(cpool, cpfds);
-  CPOOL_UNLOCK(cpool);
-  return result;
-}
-
-/* return information about the shutdown connections */
-unsigned int Curl_cpool_add_waitfds(struct cpool *cpool,
-                                    struct Curl_waitfds *cwfds)
-{
-  unsigned int need = 0;
-
-  CPOOL_LOCK(cpool);
-  if(Curl_llist_head(&cpool->shutdowns)) {
-    struct Curl_llist_node *e;
-    struct easy_pollset ps;
-    struct connectdata *conn;
-
-    for(e = Curl_llist_head(&cpool->shutdowns); e;
-        e = Curl_node_next(e)) {
-      conn = Curl_node_elem(e);
-      memset(&ps, 0, sizeof(ps));
-      Curl_attach_connection(cpool->idata, conn);
-      Curl_conn_adjust_pollset(cpool->idata, conn, &ps);
-      Curl_detach_connection(cpool->idata);
-
-      need += Curl_waitfds_add_ps(cwfds, &ps);
-    }
-  }
-  CPOOL_UNLOCK(cpool);
-  return need;
-}
-
-/* return fd_set info about the shutdown connections */
-void Curl_cpool_setfds(struct cpool *cpool,
-                       fd_set *read_fd_set, fd_set *write_fd_set,
-                       int *maxfd)
-{
-  CPOOL_LOCK(cpool);
-  if(Curl_llist_head(&cpool->shutdowns)) {
-    struct Curl_llist_node *e;
-
-    for(e = Curl_llist_head(&cpool->shutdowns); e;
-        e = Curl_node_next(e)) {
-      struct easy_pollset ps;
-      unsigned int i;
-      struct connectdata *conn = Curl_node_elem(e);
-      memset(&ps, 0, sizeof(ps));
-      Curl_attach_connection(cpool->idata, conn);
-      Curl_conn_adjust_pollset(cpool->idata, conn, &ps);
-      Curl_detach_connection(cpool->idata);
-
-      for(i = 0; i < ps.num; i++) {
-#if defined(__DJGPP__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Warith-conversion"
-#endif
-        if(ps.actions[i] & CURL_POLL_IN)
-          FD_SET(ps.sockets[i], read_fd_set);
-        if(ps.actions[i] & CURL_POLL_OUT)
-          FD_SET(ps.sockets[i], write_fd_set);
-#if defined(__DJGPP__)
-#pragma GCC diagnostic pop
-#endif
-        if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) &&
-           ((int)ps.sockets[i] > *maxfd))
-          *maxfd = (int)ps.sockets[i];
-      }
-    }
-  }
-  CPOOL_UNLOCK(cpool);
-}
-
-static void cpool_perform(struct cpool *cpool)
-{
-  struct Curl_easy *data = cpool->idata;
-  struct Curl_llist_node *e = Curl_llist_head(&cpool->shutdowns);
-  struct Curl_llist_node *enext;
-  struct connectdata *conn;
-  struct curltime *nowp = NULL;
-  struct curltime now;
-  timediff_t next_from_now_ms = 0, ms;
-  bool done;
-
-  if(!e)
-    return;
-
-  DEBUGASSERT(data);
-  CURL_TRC_M(data, "[CPOOL] perform, %zu connections being shutdown",
-             Curl_llist_count(&cpool->shutdowns));
-  while(e) {
-    enext = Curl_node_next(e);
-    conn = Curl_node_elem(e);
-    Curl_attach_connection(data, conn);
-    cpool_run_conn_shutdown(data, conn, &done);
-    CURL_TRC_M(data, "[CPOOL] shutdown, done=%d", done);
-    Curl_detach_connection(data);
-    if(done) {
-      Curl_node_remove(e);
-      cpool_close_and_destroy(cpool, conn, NULL, FALSE);
-    }
-    else {
-      /* Not done, when does this connection time out? */
-      if(!nowp) {
-        now = Curl_now();
-        nowp = &now;
-      }
-      ms = Curl_conn_shutdown_timeleft(conn, nowp);
-      if(ms && ms < next_from_now_ms)
-        next_from_now_ms = ms;
-    }
-    e = enext;
-  }
-
-  if(next_from_now_ms)
-    Curl_expire(data, next_from_now_ms, EXPIRE_RUN_NOW);
-}
-
-/*
- * Close and destroy the connection. Run the shutdown sequence once,
- * of so requested.
- */
-static void cpool_close_and_destroy(struct cpool *cpool,
-                                    struct connectdata *conn,
-                                    struct Curl_easy *data,
-                                    bool do_shutdown)
-{
-  bool done;
-
-  /* there must be a connection to close */
-  DEBUGASSERT(conn);
-  /* it must be removed from the connection pool */
-  DEBUGASSERT(!conn->bits.in_cpool);
-  /* there must be an associated transfer */
-  DEBUGASSERT(data || cpool);
-  if(!data)
-    data = cpool->idata;
-
-  /* the transfer must be detached from the connection */
-  DEBUGASSERT(data && !data->conn);
-
-  Curl_attach_connection(data, conn);
-
-  cpool_run_conn_shutdown_handler(data, conn);
-  if(do_shutdown) {
-    /* Make a last attempt to shutdown handlers and filters, if
-     * not done so already. */
-    cpool_run_conn_shutdown(data, conn, &done);
-  }
-
-  if(cpool)
-    CURL_TRC_M(data, "[CPOOL] closing connection");
-  else
-    DEBUGF(infof(data, "closing connection #%" FMT_OFF_T,
-                 conn->connection_id));
-  Curl_conn_close(data, SECONDARYSOCKET);
-  Curl_conn_close(data, FIRSTSOCKET);
-  Curl_detach_connection(data);
-
-  if(cpool && cpool->multi)
-    Curl_multi_ev_conn_done(cpool->multi, data, conn);
-  else if(data->multi)
-    Curl_multi_ev_conn_done(data->multi, data, conn);
-
-  Curl_conn_free(data, conn);
-
-  if(cpool && cpool->multi) {
-    CURL_TRC_M(data, "[CPOOL] trigger multi connchanged");
-    Curl_multi_connchanged(cpool->multi);
-  }
-}
-
-
-static CURLMcode cpool_update_shutdown_ev(struct cpool *cpool,
-                                          struct Curl_multi *multi,
-                                          struct connectdata *conn)
-{
-  CURLMcode mresult;
-
-  DEBUGASSERT(cpool);
-  DEBUGASSERT(multi);
-  DEBUGASSERT(multi->socket_cb);
-
-  Curl_attach_connection(cpool->idata, conn);
-  mresult = Curl_multi_ev_assess_conn(multi, cpool->idata, conn);
-  Curl_detach_connection(cpool->idata);
-  return mresult;
-}
-
-static void cpool_multi_socket(struct Curl_multi *multi, curl_socket_t s)
-{
-  struct cpool *cpool = &multi->cpool;
-  struct Curl_llist_node *e;
-  struct connectdata *conn;
-  bool done;
-
-  DEBUGASSERT(multi->socket_cb);
-  CPOOL_LOCK(cpool);
-  e = Curl_llist_head(&cpool->shutdowns);
-  while(e) {
-    conn = Curl_node_elem(e);
-    if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) {
-      Curl_attach_connection(cpool->idata, conn);
-      cpool_run_conn_shutdown(cpool->idata, conn, &done);
-      CURL_TRC_M(cpool->idata, "[CPOOL] shutdown, done=%d", done);
-      Curl_detach_connection(cpool->idata);
-      if(done || cpool_update_shutdown_ev(cpool, multi, conn)) {
-        Curl_node_remove(e);
-        cpool_close_and_destroy(cpool, conn, NULL, FALSE);
-      }
-      break;
-    }
-    e = Curl_node_next(e);
-  }
-  CPOOL_UNLOCK(cpool);
-}
-
-void Curl_cpool_multi_perform(struct Curl_multi *multi, curl_socket_t s)
-{
-  CPOOL_LOCK(&multi->cpool);
-  if((s == CURL_SOCKET_TIMEOUT) || (!multi->socket_cb))
-    cpool_perform(&multi->cpool);
-  else
-    cpool_multi_socket(multi, s);
-  CPOOL_UNLOCK(&multi->cpool);
-}
-
-
-#define NUM_POLLS_ON_STACK 10
-
-static CURLcode cpool_shutdown_wait(struct cpool *cpool, int timeout_ms)
-{
-  struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK];
-  struct curl_pollfds cpfds;
-  CURLcode result;
-
-  Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK);
-
-  result = cpool_add_pollfds(cpool, &cpfds);
-  if(result)
-    goto out;
-
-  Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000));
-
-out:
-  Curl_pollfds_cleanup(&cpfds);
-  return result;
-}
-
-static void cpool_shutdown_all(struct cpool *cpool,
-                               struct Curl_easy *data, int timeout_ms)
-{
-  struct connectdata *conn;
-  struct curltime started = Curl_now();
-
-  if(!data)
-    return;
-  (void)data;
-
-  CURL_TRC_M(data, "[CPOOL] shutdown all");
-
-  /* Move all connections into the shutdown queue */
-  for(conn = cpool_get_live_conn(cpool); conn;
-      conn = cpool_get_live_conn(cpool)) {
-    /* Move conn from live set to shutdown or destroy right away */
-    CURL_TRC_M(data, "[CPOOL] moving connection to shutdown queue");
-    cpool_remove_conn(cpool, conn);
-    cpool_discard_conn(cpool, data, conn, FALSE);
-  }
-
-  while(Curl_llist_head(&cpool->shutdowns)) {
-    timediff_t timespent;
-    int remain_ms;
-
-    cpool_perform(cpool);
-
-    if(!Curl_llist_head(&cpool->shutdowns)) {
-      CURL_TRC_M(data, "[CPOOL] shutdown finished cleanly");
-      break;
-    }
-
-    /* wait for activity, timeout or "nothing" */
-    timespent = Curl_timediff(Curl_now(), started);
-    if(timespent >= (timediff_t)timeout_ms) {
-      CURL_TRC_M(data, "[CPOOL] shutdown finished, %s",
-                (timeout_ms > 0) ? "timeout" : "best effort done");
-      break;
-    }
-
-    remain_ms = timeout_ms - (int)timespent;
-    if(cpool_shutdown_wait(cpool, remain_ms)) {
-      CURL_TRC_M(data, "[CPOOL] shutdown finished, aborted");
-      break;
-    }
-  }
-
-  /* Due to errors/timeout, we might come here without being done. */
-  cpool_shutdown_discard_all(cpool);
-}
 
 struct cpool_reaper_ctx {
   struct curltime now;
@@ -1252,7 +696,7 @@ static int cpool_reap_dead_cb(struct Curl_easy *data,
   struct cpool_reaper_ctx *rctx = param;
   if(Curl_conn_seems_dead(conn, data, &rctx->now)) {
     /* stop the iteration here, pass back the connection that was pruned */
-    Curl_cpool_disconnect(data, conn, FALSE);
+    Curl_conn_terminate(data, conn, FALSE);
     return 1;
   }
   return 0; /* continue iteration */
@@ -1275,7 +719,7 @@ void Curl_cpool_prune_dead(struct Curl_easy *data)
     return;
 
   rctx.now = Curl_now();
-  CPOOL_LOCK(cpool);
+  CPOOL_LOCK(cpool, data);
   elapsed = Curl_timediff(rctx.now, cpool->last_cleanup);
 
   if(elapsed >= 1000L) {
@@ -1283,7 +727,7 @@ void Curl_cpool_prune_dead(struct Curl_easy *data)
       ;
     cpool->last_cleanup = rctx.now;
   }
-  CPOOL_UNLOCK(cpool);
+  CPOOL_UNLOCK(cpool, data);
 }
 
 static int conn_upkeep(struct Curl_easy *data,
@@ -1303,9 +747,9 @@ CURLcode Curl_cpool_upkeep(void *data)
   if(!cpool)
     return CURLE_OK;
 
-  CPOOL_LOCK(cpool);
+  CPOOL_LOCK(cpool, data);
   cpool_foreach(data, cpool, &now, conn_upkeep);
-  CPOOL_UNLOCK(cpool);
+  CPOOL_UNLOCK(cpool, data);
   return CURLE_OK;
 }
 
@@ -1336,9 +780,9 @@ struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
     return NULL;
   fctx.id = conn_id;
   fctx.conn = NULL;
-  CPOOL_LOCK(cpool);
-  cpool_foreach(cpool->idata, cpool, &fctx, cpool_find_conn);
-  CPOOL_UNLOCK(cpool);
+  CPOOL_LOCK(cpool, data);
+  cpool_foreach(data, cpool, &fctx, cpool_find_conn);
+  CPOOL_UNLOCK(cpool, data);
   return fctx.conn;
 }
 
@@ -1371,9 +815,9 @@ void Curl_cpool_do_by_id(struct Curl_easy *data, curl_off_t conn_id,
   dctx.id = conn_id;
   dctx.cb = cb;
   dctx.cbdata = cbdata;
-  CPOOL_LOCK(cpool);
+  CPOOL_LOCK(cpool, data);
   cpool_foreach(data, cpool, &dctx, cpool_do_conn);
-  CPOOL_UNLOCK(cpool);
+  CPOOL_UNLOCK(cpool, data);
 }
 
 void Curl_cpool_do_locked(struct Curl_easy *data,
@@ -1382,9 +826,9 @@ void Curl_cpool_do_locked(struct Curl_easy *data,
 {
   struct cpool *cpool = cpool_get_instance(data);
   if(cpool) {
-    CPOOL_LOCK(cpool);
+    CPOOL_LOCK(cpool, data);
     cb(conn, data, cbdata);
-    CPOOL_UNLOCK(cpool);
+    CPOOL_UNLOCK(cpool, data);
   }
   else
     cb(conn, data, cbdata);
index d12328cd4115f0c21239c4490434edc5465e588d..ab99c309af7d27c324b170681f949ae13d35e17f 100644 (file)
@@ -35,6 +35,19 @@ struct Curl_waitfds;
 struct Curl_multi;
 struct Curl_share;
 
+/**
+ * Terminate the connection, e.g. close and destroy.
+ * If the connection is in a cpool, remove it.
+ * If a `cshutdn` is available (e.g. data has a multi handle),
+ * pass the connection to that for controlled shutdown.
+ * Otherwise terminate it right away.
+ * Takes ownership of `conn`.
+ * `data` should not be attached to a connection.
+ */
+void Curl_conn_terminate(struct Curl_easy *data,
+                         struct connectdata *conn,
+                         bool aborted);
+
 /**
  * Callback invoked when disconnecting connections.
  * @param data    transfer last handling the connection, not attached
@@ -54,12 +67,11 @@ struct cpool {
   curl_off_t next_connection_id;
   curl_off_t next_easy_id;
   struct curltime last_cleanup;
-  struct Curl_llist shutdowns;  /* The connections being shut down */
-  struct Curl_easy *idata; /* internal handle used for discard */
-  struct Curl_multi *multi; /* != NULL iff pool belongs to multi */
+  struct Curl_easy *idata; /* internal handle for maintenance */
   struct Curl_share *share; /* != NULL iff pool belongs to share */
   Curl_cpool_disconnect_cb *disconnect_cb;
   BIT(locked);
+  BIT(initialised);
 };
 
 /* Init the pool, pass multi only if pool is owned by it.
@@ -67,7 +79,7 @@ struct cpool {
  */
 int Curl_cpool_init(struct cpool *cpool,
                     Curl_cpool_disconnect_cb *disconnect_cb,
-                    struct Curl_multi *multi,
+                    struct Curl_easy *idata,
                     struct Curl_share *share,
                     size_t size);
 
@@ -78,14 +90,13 @@ void Curl_cpool_destroy(struct cpool *connc);
  * Assigns `data->id`. */
 void Curl_cpool_xfer_init(struct Curl_easy *data);
 
-/**
- * Get the connection with the given id from the transfer's pool.
- */
+/* Get the connection with the given id from `data`'s conn pool. */
 struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
                                         curl_off_t conn_id);
 
-CURLcode Curl_cpool_add_conn(struct Curl_easy *data,
-                             struct connectdata *conn) WARN_UNUSED_RESULT;
+/* Add the connection to the pool. */
+CURLcode Curl_cpool_add(struct Curl_easy *data,
+                        struct connectdata *conn) WARN_UNUSED_RESULT;
 
 /**
  * Return if the pool has reached its configured limits for adding
@@ -131,17 +142,6 @@ bool Curl_cpool_find(struct Curl_easy *data,
 bool Curl_cpool_conn_now_idle(struct Curl_easy *data,
                               struct connectdata *conn);
 
-/**
- * Remove the connection from the pool and tear it down.
- * If `aborted` is FALSE, the connection will be shut down first
- * before closing and destroying it.
- * If the shutdown is not immediately complete, the connection
- * will be placed into the pool's shutdown queue.
- */
-void Curl_cpool_disconnect(struct Curl_easy *data,
-                           struct connectdata *conn,
-                           bool aborted);
-
 /**
  * This function scans the data's connection pool for half-open/dead
  * connections, closes and removes them.
@@ -178,22 +178,4 @@ void Curl_cpool_do_locked(struct Curl_easy *data,
                           struct connectdata *conn,
                           Curl_cpool_conn_do_cb *cb, void *cbdata);
 
-/**
- * Add sockets and POLLIN/OUT flags for connections handled by the pool.
- */
-CURLcode Curl_cpool_add_pollfds(struct cpool *connc,
-                                struct curl_pollfds *cpfds);
-unsigned int Curl_cpool_add_waitfds(struct cpool *connc,
-                                    struct Curl_waitfds *cwfds);
-
-void Curl_cpool_setfds(struct cpool *cpool,
-                       fd_set *read_fd_set, fd_set *write_fd_set,
-                       int *maxfd);
-
-/**
- * Run connections on socket. If socket is CURL_SOCKET_TIMEOUT, run
- * maintenance on all connections.
- */
-void Curl_cpool_multi_perform(struct Curl_multi *multi, curl_socket_t s);
-
 #endif /* HEADER_CURL_CONNCACHE_H */
index 88ebf524312b0ea35057f80b4fb505c3ffabd4c4..076e67c579bc946d507544d4f025b761feb02fd4 100644 (file)
@@ -161,7 +161,7 @@ timediff_t Curl_timeleft(struct Curl_easy *data,
 }
 
 void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
-                         struct curltime *nowp)
+                         int timeout_ms, struct curltime *nowp)
 {
   struct curltime now;
 
@@ -171,8 +171,13 @@ void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
     nowp = &now;
   }
   data->conn->shutdown.start[sockindex] = *nowp;
-  data->conn->shutdown.timeout_ms = (data->set.shutdowntimeout > 0) ?
-    data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS;
+  data->conn->shutdown.timeout_ms = (timeout_ms >= 0) ?
+    (unsigned int)timeout_ms :
+    ((data->set.shutdowntimeout > 0) ?
+     data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS);
+  if(data->conn->shutdown.timeout_ms)
+    Curl_expire_ex(data, nowp, data->conn->shutdown.timeout_ms,
+                   EXPIRE_SHUTDOWN);
 }
 
 timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex,
index cbfe294c92722ab1099014cf2f4c48e533badd0a..6a6d6d50df667ff7d187bcb51bb22940218a9664 100644 (file)
@@ -45,7 +45,7 @@ timediff_t Curl_timeleft(struct Curl_easy *data,
 #define DEFAULT_SHUTDOWN_TIMEOUT_MS   (2 * 1000)
 
 void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
-                         struct curltime *nowp);
+                         int timeout_ms, struct curltime *nowp);
 
 /* return how much time there is left to shutdown the connection at
  * sockindex. Returns 0 if there is no limit or shutdown has not started. */
diff --git a/lib/cshutdn.c b/lib/cshutdn.c
new file mode 100644 (file)
index 0000000..9d4a5ec
--- /dev/null
@@ -0,0 +1,566 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se>
+ * 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
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#include <curl/curl.h>
+
+#include "urldata.h"
+#include "url.h"
+#include "cfilters.h"
+#include "progress.h"
+#include "multiif.h"
+#include "multi_ev.h"
+#include "sendf.h"
+#include "cshutdn.h"
+#include "http_negotiate.h"
+#include "http_ntlm.h"
+#include "sigpipe.h"
+#include "connect.h"
+#include "select.h"
+#include "strcase.h"
+#include "strparse.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+static void cshutdn_run_conn_handler(struct Curl_easy *data,
+                                     struct connectdata *conn)
+{
+  if(!conn->bits.shutdown_handler) {
+    if(conn->dns_entry)
+      Curl_resolv_unlink(data, &conn->dns_entry);
+
+    /* Cleanup NTLM connection-related data */
+    Curl_http_auth_cleanup_ntlm(conn);
+
+    /* Cleanup NEGOTIATE connection-related data */
+    Curl_http_auth_cleanup_negotiate(conn);
+
+    if(conn->handler && conn->handler->disconnect) {
+      /* Some disconnect handlers do a blocking wait on server responses.
+       * FTP/IMAP/SMTP and SFTP are among them. When using the internal
+       * handle, set an overall short timeout so we do not hang for the
+       * default 120 seconds. */
+      if(data->state.internal) {
+        data->set.timeout = DEFAULT_SHUTDOWN_TIMEOUT_MS;
+        (void)Curl_pgrsTime(data, TIMER_STARTOP);
+      }
+
+      /* This is set if protocol-specific cleanups should be made */
+      DEBUGF(infof(data, "connection #%" FMT_OFF_T
+                   ", shutdown protocol handler (aborted=%d)",
+                   conn->connection_id, conn->bits.aborted));
+      /* There are protocol handlers that block on retrieving
+       * server responses here (FTP). Set a short timeout. */
+      conn->handler->disconnect(data, conn, conn->bits.aborted);
+    }
+
+    /* possible left-overs from the async name resolvers */
+    Curl_resolver_cancel(data);
+
+    conn->bits.shutdown_handler = TRUE;
+  }
+}
+
+static void cshutdn_run_once(struct Curl_easy *data,
+                             struct connectdata *conn,
+                             bool *done)
+{
+  CURLcode r1, r2;
+  bool done1, done2;
+
+  /* We expect to be attached when called */
+  DEBUGASSERT(data->conn == conn);
+
+  cshutdn_run_conn_handler(data, conn);
+
+  if(conn->bits.shutdown_filters) {
+    *done = TRUE;
+    return;
+  }
+
+  if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET))
+    r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1);
+  else {
+    r1 = CURLE_OK;
+    done1 = TRUE;
+  }
+
+  if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET))
+    r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2);
+  else {
+    r2 = CURLE_OK;
+    done2 = TRUE;
+  }
+
+  /* we are done when any failed or both report success */
+  *done = (r1 || r2 || (done1 && done2));
+  if(*done)
+    conn->bits.shutdown_filters = TRUE;
+}
+
+void Curl_cshutdn_run_once(struct Curl_easy *data,
+                      struct connectdata *conn,
+                      bool *done)
+{
+  DEBUGASSERT(!data->conn);
+  Curl_attach_connection(data, conn);
+  cshutdn_run_once(data, conn, done);
+  CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done);
+  Curl_detach_connection(data);
+}
+
+
+void Curl_cshutdn_terminate(struct Curl_easy *data,
+                            struct connectdata *conn,
+                            bool do_shutdown)
+{
+  struct Curl_easy *admin = data;
+  bool done;
+
+  /* there must be a connection to close */
+  DEBUGASSERT(conn);
+  /* it must be removed from the connection pool */
+  DEBUGASSERT(!conn->bits.in_cpool);
+  /* the transfer must be detached from the connection */
+  DEBUGASSERT(data && !data->conn);
+
+  /* If we can obtain an internal admin handle, use that to attach
+   * and terminate the connection. Some protocol will try to mess with
+   * `data` during shutdown and we do not want that with a `data` from
+   * the application. */
+  if(data->multi && data->multi->admin)
+    admin = data->multi->admin;
+
+  Curl_attach_connection(admin, conn);
+
+  cshutdn_run_conn_handler(admin, conn);
+  if(do_shutdown) {
+    /* Make a last attempt to shutdown handlers and filters, if
+     * not done so already. */
+    cshutdn_run_once(admin, conn, &done);
+  }
+  CURL_TRC_M(admin, "[SHUTDOWN] closing connection");
+  Curl_conn_close(admin, SECONDARYSOCKET);
+  Curl_conn_close(admin, FIRSTSOCKET);
+  Curl_detach_connection(admin);
+
+  if(data->multi)
+    Curl_multi_ev_conn_done(data->multi, data, conn);
+  Curl_conn_free(admin, conn);
+
+  if(data->multi) {
+    CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged");
+    Curl_multi_connchanged(data->multi);
+  }
+}
+
+static void cshutdn_destroy_oldest(struct cshutdn *cshutdn,
+                                     struct Curl_easy *data)
+{
+  struct Curl_llist_node *e;
+  struct connectdata *conn;
+
+  e = Curl_llist_head(&cshutdn->list);
+  if(e) {
+    SIGPIPE_VARIABLE(pipe_st);
+    conn = Curl_node_elem(e);
+    Curl_node_remove(e);
+    sigpipe_init(&pipe_st);
+    sigpipe_apply(data, &pipe_st);
+    Curl_cshutdn_terminate(data, conn, FALSE);
+    sigpipe_restore(&pipe_st);
+  }
+}
+
+#define NUM_POLLS_ON_STACK 10
+
+static CURLcode cshutdn_wait(struct cshutdn *cshutdn,
+                               struct Curl_easy *data,
+                               int timeout_ms)
+{
+  struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK];
+  struct curl_pollfds cpfds;
+  CURLcode result;
+
+  Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK);
+
+  result = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds);
+  if(result)
+    goto out;
+
+  Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000));
+
+out:
+  Curl_pollfds_cleanup(&cpfds);
+  return result;
+}
+
+
+static void cshutdn_perform(struct cshutdn *cshutdn,
+                              struct Curl_easy *data)
+{
+  struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list);
+  struct Curl_llist_node *enext;
+  struct connectdata *conn;
+  struct curltime *nowp = NULL;
+  struct curltime now;
+  timediff_t next_expire_ms = 0, ms;
+  bool done;
+
+  if(!e)
+    return;
+
+  CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections",
+             Curl_llist_count(&cshutdn->list));
+  while(e) {
+    enext = Curl_node_next(e);
+    conn = Curl_node_elem(e);
+    Curl_cshutdn_run_once(data, conn, &done);
+    if(done) {
+      Curl_node_remove(e);
+      Curl_cshutdn_terminate(data, conn, FALSE);
+    }
+    else {
+      /* idata has one timer list, but maybe more than one connection.
+       * Set EXPIRE_SHUTDOWN to the smallest time left for all. */
+      if(!nowp) {
+        now = Curl_now();
+        nowp = &now;
+      }
+      ms = Curl_conn_shutdown_timeleft(conn, nowp);
+      if(ms && ms < next_expire_ms)
+        next_expire_ms = ms;
+    }
+    e = enext;
+  }
+
+  if(next_expire_ms)
+    Curl_expire_ex(data, nowp, next_expire_ms, EXPIRE_SHUTDOWN);
+}
+
+
+static void cshutdn_terminate_all(struct cshutdn *cshutdn,
+                                  struct Curl_easy *data,
+                                  int timeout_ms)
+{
+  struct curltime started = Curl_now();
+  struct Curl_llist_node *e;
+  SIGPIPE_VARIABLE(pipe_st);
+
+  DEBUGASSERT(cshutdn);
+  DEBUGASSERT(data);
+
+  CURL_TRC_M(data, "[SHUTDOWN] shutdown all");
+  sigpipe_init(&pipe_st);
+  sigpipe_apply(data, &pipe_st);
+
+  while(Curl_llist_head(&cshutdn->list)) {
+    timediff_t timespent;
+    int remain_ms;
+
+    cshutdn_perform(cshutdn, data);
+
+    if(!Curl_llist_head(&cshutdn->list)) {
+      CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly");
+      break;
+    }
+
+    /* wait for activity, timeout or "nothing" */
+    timespent = Curl_timediff(Curl_now(), started);
+    if(timespent >= (timediff_t)timeout_ms) {
+      CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s",
+                (timeout_ms > 0) ? "timeout" : "best effort done");
+      break;
+    }
+
+    remain_ms = timeout_ms - (int)timespent;
+    if(cshutdn_wait(cshutdn, data, remain_ms)) {
+      CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted");
+      break;
+    }
+  }
+
+  /* Terminate any remaining. */
+  e = Curl_llist_head(&cshutdn->list);
+  while(e) {
+    struct connectdata *conn = Curl_node_elem(e);
+    Curl_node_remove(e);
+    Curl_cshutdn_terminate(data, conn, FALSE);
+    e = Curl_llist_head(&cshutdn->list);
+  }
+  DEBUGASSERT(!Curl_llist_count(&cshutdn->list));
+
+  Curl_hostcache_clean(data, data->dns.hostcache);
+
+  sigpipe_restore(&pipe_st);
+}
+
+
+int Curl_cshutdn_init(struct cshutdn *cshutdn,
+                      struct Curl_multi *multi)
+{
+  DEBUGASSERT(multi);
+  cshutdn->multi = multi;
+  Curl_llist_init(&cshutdn->list, NULL);
+  cshutdn->initialised = TRUE;
+  return 0; /* good */
+}
+
+
+void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
+                          struct Curl_easy *data)
+{
+  if(cshutdn->initialised && data) {
+    int timeout_ms = 0;
+    /* Just for testing, run graceful shutdown */
+#ifdef DEBUGBUILD
+    {
+      const char *p = getenv("CURL_GRACEFUL_SHUTDOWN");
+      if(p) {
+        curl_off_t l;
+        if(!Curl_str_number(&p, &l, INT_MAX))
+          timeout_ms = (int)l;
+      }
+    }
+#endif
+
+    CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms",
+               Curl_llist_count(&cshutdn->list), timeout_ms);
+    cshutdn_terminate_all(cshutdn, data, timeout_ms);
+  }
+  cshutdn->multi = NULL;
+}
+
+size_t Curl_cshutdn_count(struct Curl_easy *data)
+{
+  if(data && data->multi) {
+    struct cshutdn *csd = &data->multi->cshutdn;
+    return Curl_llist_count(&csd->list);
+  }
+  return 0;
+}
+
+size_t Curl_cshutdn_dest_count(struct Curl_easy *data,
+                               const char *destination)
+{
+  if(data && data->multi) {
+    struct cshutdn *csd = &data->multi->cshutdn;
+    size_t n = 0;
+    struct Curl_llist_node *e = Curl_llist_head(&csd->list);
+    while(e) {
+      struct connectdata *conn = Curl_node_elem(e);
+      if(!strcmp(destination, conn->destination))
+        ++n;
+      e = Curl_node_next(e);
+    }
+    return n;
+  }
+  return 0;
+}
+
+
+static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn,
+                                     struct Curl_easy *data,
+                                     struct connectdata *conn)
+{
+  CURLMcode mresult;
+
+  DEBUGASSERT(cshutdn);
+  DEBUGASSERT(cshutdn->multi->socket_cb);
+
+  Curl_attach_connection(data, conn);
+  mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn);
+  Curl_detach_connection(data);
+  return mresult;
+}
+
+
+void Curl_cshutdn_add(struct cshutdn *cshutdn,
+                        struct connectdata *conn,
+                        size_t conns_in_pool)
+{
+  struct Curl_easy *data = cshutdn->multi->admin;
+  size_t max_total = (cshutdn->multi->max_total_connections > 0) ?
+                     (size_t)cshutdn->multi->max_total_connections : 0;
+
+  /* Add the connection to our shutdown list for non-blocking shutdown
+   * during multi processing. */
+  if(max_total > 0 && (max_total <=
+        (conns_in_pool + Curl_llist_count(&cshutdn->list)))) {
+    CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection "
+               "due to connection limit of %zu", max_total);
+    cshutdn_destroy_oldest(cshutdn, data);
+  }
+
+  if(cshutdn->multi->socket_cb) {
+    if(cshutdn_update_ev(cshutdn, data, conn)) {
+      CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%"
+                 FMT_OFF_T, conn->connection_id);
+      Curl_cshutdn_terminate(data, conn, FALSE);
+      return;
+    }
+  }
+
+  Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node);
+  CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T
+             " to shutdowns, now %zu conns in shutdown",
+             conn->connection_id, Curl_llist_count(&cshutdn->list));
+}
+
+
+static void cshutdn_multi_socket(struct cshutdn *cshutdn,
+                                   struct Curl_easy *data,
+                                   curl_socket_t s)
+{
+  struct Curl_llist_node *e;
+  struct connectdata *conn;
+  bool done;
+
+  DEBUGASSERT(cshutdn->multi->socket_cb);
+  e = Curl_llist_head(&cshutdn->list);
+  while(e) {
+    conn = Curl_node_elem(e);
+    if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) {
+      Curl_cshutdn_run_once(data, conn, &done);
+      if(done || cshutdn_update_ev(cshutdn, data, conn)) {
+        Curl_node_remove(e);
+        Curl_cshutdn_terminate(data, conn, FALSE);
+      }
+      break;
+    }
+    e = Curl_node_next(e);
+  }
+}
+
+
+void Curl_cshutdn_perform(struct cshutdn *cshutdn,
+                          struct Curl_easy *data,
+                          curl_socket_t s)
+{
+  if((s == CURL_SOCKET_TIMEOUT) || (!cshutdn->multi->socket_cb))
+    cshutdn_perform(cshutdn, data);
+  else
+    cshutdn_multi_socket(cshutdn, data, s);
+}
+
+/* return fd_set info about the shutdown connections */
+void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
+                         struct Curl_easy *data,
+                         fd_set *read_fd_set, fd_set *write_fd_set,
+                         int *maxfd)
+{
+  if(Curl_llist_head(&cshutdn->list)) {
+    struct Curl_llist_node *e;
+
+    for(e = Curl_llist_head(&cshutdn->list); e;
+        e = Curl_node_next(e)) {
+      struct easy_pollset ps;
+      unsigned int i;
+      struct connectdata *conn = Curl_node_elem(e);
+      memset(&ps, 0, sizeof(ps));
+      Curl_attach_connection(data, conn);
+      Curl_conn_adjust_pollset(data, conn, &ps);
+      Curl_detach_connection(data);
+
+      for(i = 0; i < ps.num; i++) {
+#if defined(__DJGPP__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warith-conversion"
+#endif
+        if(ps.actions[i] & CURL_POLL_IN)
+          FD_SET(ps.sockets[i], read_fd_set);
+        if(ps.actions[i] & CURL_POLL_OUT)
+          FD_SET(ps.sockets[i], write_fd_set);
+#if defined(__DJGPP__)
+#pragma GCC diagnostic pop
+#endif
+        if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) &&
+           ((int)ps.sockets[i] > *maxfd))
+          *maxfd = (int)ps.sockets[i];
+      }
+    }
+  }
+}
+
+/* return information about the shutdown connections */
+unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
+                                      struct Curl_easy *data,
+                                      struct Curl_waitfds *cwfds)
+{
+  unsigned int need = 0;
+
+  if(Curl_llist_head(&cshutdn->list)) {
+    struct Curl_llist_node *e;
+    struct easy_pollset ps;
+    struct connectdata *conn;
+
+    for(e = Curl_llist_head(&cshutdn->list); e;
+        e = Curl_node_next(e)) {
+      conn = Curl_node_elem(e);
+      memset(&ps, 0, sizeof(ps));
+      Curl_attach_connection(data, conn);
+      Curl_conn_adjust_pollset(data, conn, &ps);
+      Curl_detach_connection(data);
+
+      need += Curl_waitfds_add_ps(cwfds, &ps);
+    }
+  }
+  return need;
+}
+
+CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
+                                  struct Curl_easy *data,
+                                  struct curl_pollfds *cpfds)
+{
+  CURLcode result = CURLE_OK;
+
+  if(Curl_llist_head(&cshutdn->list)) {
+    struct Curl_llist_node *e;
+    struct easy_pollset ps;
+    struct connectdata *conn;
+
+    for(e = Curl_llist_head(&cshutdn->list); e;
+        e = Curl_node_next(e)) {
+      conn = Curl_node_elem(e);
+      memset(&ps, 0, sizeof(ps));
+      Curl_attach_connection(data, conn);
+      Curl_conn_adjust_pollset(data, conn, &ps);
+      Curl_detach_connection(data);
+
+      result = Curl_pollfds_add_ps(cpfds, &ps);
+      if(result) {
+        Curl_pollfds_cleanup(cpfds);
+        goto out;
+      }
+    }
+  }
+out:
+  return result;
+}
diff --git a/lib/cshutdn.h b/lib/cshutdn.h
new file mode 100644 (file)
index 0000000..202e869
--- /dev/null
@@ -0,0 +1,104 @@
+#ifndef HEADER_CURL_CSHUTDN_H
+#define HEADER_CURL_CSHUTDN_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se>
+ *
+ * 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
+ *
+ ***************************************************************************/
+
+#include <curl/curl.h>
+#include "timeval.h"
+
+struct connectdata;
+struct Curl_easy;
+struct curl_pollfds;
+struct Curl_waitfds;
+struct Curl_multi;
+struct Curl_share;
+
+/* Run the shutdown of the connection once.
+ * Will shortly attach/detach `data` to `conn` while doing so.
+ * `done` will be set TRUE if any error was encountered or if
+ * the connection was shut down completely. */
+void Curl_cshutdn_run_once(struct Curl_easy *data,
+                           struct connectdata *conn,
+                           bool *done);
+
+/* Terminates the connection, e.g. closes and destroys it.
+ * If `run_shutdown` is TRUE, the shutdown will be run once before
+ * terminating it.
+ * Takes ownership of `conn`. */
+void Curl_cshutdn_terminate(struct Curl_easy *data,
+                            struct connectdata *conn,
+                            bool run_shutdown);
+
+/* A `cshutdown` is always owned by a multi handle to maintain
+ * the connections to be shut down. It registers timers and
+ * sockets to monitor via the multi handle. */
+struct cshutdn {
+  struct Curl_llist list;    /* connections being shut down */
+  struct Curl_multi *multi;  /* the multi owning this */
+  BIT(initialised);
+};
+
+/* Init as part of the given multi handle. */
+int Curl_cshutdn_init(struct cshutdn *cshutdn,
+                      struct Curl_multi *multi);
+
+/* Terminate all remaining connections and free resources. */
+void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
+                          struct Curl_easy *data);
+
+/* Number of connections being shut down. */
+size_t Curl_cshutdn_count(struct Curl_easy *data);
+
+/* Number of connections to the destination being shut down. */
+size_t Curl_cshutdn_dest_count(struct Curl_easy *data,
+                               const char *destination);
+
+/* Add a connection to have it shut down. Will terminate the oldest
+ * connection when total connection limit of multi is being reached. */
+void Curl_cshutdn_add(struct cshutdn *cshutdn,
+                      struct connectdata *conn,
+                      size_t conns_in_pool);
+
+/* Add sockets and POLLIN/OUT flags for connections being shut down. */
+CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
+                                  struct Curl_easy *data,
+                                  struct curl_pollfds *cpfds);
+
+unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
+                                      struct Curl_easy *data,
+                                      struct Curl_waitfds *cwfds);
+
+void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
+                         struct Curl_easy *data,
+                         fd_set *read_fd_set, fd_set *write_fd_set,
+                         int *maxfd);
+
+/* Run shut down connections using socket. If socket is CURL_SOCKET_TIMEOUT,
+ * run maintenance on all connections. */
+void Curl_cshutdn_perform(struct cshutdn *cshutdn,
+                          struct Curl_easy *data,
+                          curl_socket_t s);
+
+#endif /* HEADER_CURL_CSHUTDN_H */
index 36619d44cc5c822321c9403fd23b8663a161ef5b..2665b7ff7fa8dc39e35d0c86eb2bb785789cfd38 100644 (file)
@@ -770,7 +770,7 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events)
     Curl_detach_connection(data);
     s = Curl_getconnectinfo(data, &c);
     if((s != CURL_SOCKET_BAD) && c) {
-      Curl_cpool_disconnect(data, c, TRUE);
+      Curl_conn_terminate(data, c, TRUE);
     }
     DEBUGASSERT(!data->conn);
   }
index c50f5f8de31ff2cc943939390d2bd581063d3951..1dbc56ef69f9a574b1f813538ae715ec0e88087e 100644 (file)
--- a/lib/ftp.c
+++ b/lib/ftp.c
@@ -587,8 +587,9 @@ static CURLcode ftp_readresp(struct Curl_easy *data,
   }
 #endif
 
-  /* store the latest code for later retrieval */
-  data->info.httpcode = code;
+  /* store the latest code for later retrieval, except during shutdown */
+  if(!data->conn->proto.ftpc.shutdown)
+    data->info.httpcode = code;
 
   if(ftpcode)
     *ftpcode = code;
@@ -3131,6 +3132,8 @@ static CURLcode ftp_block_statemach(struct Curl_easy *data,
   CURLcode result = CURLE_OK;
 
   while(ftpc->state != FTP_STOP) {
+    if(ftpc->shutdown)
+      CURL_TRC_FTP(data, "in shutdown, waiting for server response");
     result = Curl_pp_statemach(data, pp, TRUE, TRUE /* disconnecting */);
     if(result)
       break;
@@ -4042,6 +4045,7 @@ static CURLcode ftp_quit(struct Curl_easy *data, struct connectdata *conn)
   CURLcode result = CURLE_OK;
 
   if(conn->proto.ftpc.ctl_valid) {
+    CURL_TRC_FTP(data, "sending QUIT to close session");
     result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "QUIT");
     if(result) {
       failf(data, "Failure sending QUIT command: %s",
@@ -4081,6 +4085,7 @@ static CURLcode ftp_disconnect(struct Curl_easy *data,
      ftp_quit() will check the state of ftp->ctl_valid. If it is ok it
      will try to send the QUIT command, otherwise it will just return.
   */
+  ftpc->shutdown = TRUE;
   if(dead_connection)
     ftpc->ctl_valid = FALSE;
 
index 3d0af015878679956731446306757cb01cfe55de..10d62e28c7016e9a261038bda3302192bdb7ed47 100644 (file)
--- a/lib/ftp.h
+++ b/lib/ftp.h
@@ -160,6 +160,7 @@ struct ftp_conn {
   BIT(cwdfail);     /* set TRUE if a CWD command fails, as then we must prevent
                        caching the current directory */
   BIT(wait_data_conn); /* this is set TRUE if data connection is waited */
+  BIT(shutdown);    /* connection is being shutdown, e.g. QUIT */
 };
 
 #define DEFAULT_ACCEPT_TIMEOUT   60000 /* milliseconds == one minute */
index 60697552f46408c4464f5ce969dacd49224b89d4..99da27403fb091d37cadc4a192bf575ee3a2fad9 100644 (file)
@@ -1450,7 +1450,7 @@ CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done)
 
   if(result) {
     Curl_detach_connection(data);
-    Curl_cpool_disconnect(data, conn, TRUE);
+    Curl_conn_terminate(data, conn, TRUE);
   }
   return result;
 }
index 71bcd5bc10f8f9902e19a303c26b336e00f4ec0b..ae221d4f4672e92e5df96389f99453284497d635 100644 (file)
@@ -227,8 +227,23 @@ struct Curl_multi *Curl_multi_handle(size_t ev_hashsize,  /* event hash */
   Curl_hash_init(&multi->proto_hash, 23,
                  Curl_hash_str, Curl_str_key_compare, ph_freeentry);
 
+  multi->admin = curl_easy_init();
+  if(!multi->admin)
+    goto error;
+  /* Initialize admin handle to operate inside this multi */
+  multi->admin->multi = multi;
+  multi->admin->state.internal = TRUE;
+  Curl_llist_init(&multi->admin->state.timeoutlist, NULL);
+#ifdef DEBUGBUILD
+  if(getenv("CURL_DEBUG"))
+    multi->admin->set.verbose = TRUE;
+#endif
+
+  if(Curl_cshutdn_init(&multi->cshutdn, multi))
+    goto error;
+
   if(Curl_cpool_init(&multi->cpool, Curl_on_disconnect,
-                     multi, NULL, chashsize))
+                     multi->admin, NULL, chashsize))
     goto error;
 
   if(Curl_ssl_scache_create(sesssize, 2, &multi->ssl_scache))
@@ -264,7 +279,13 @@ error:
   Curl_hash_destroy(&multi->proto_hash);
   Curl_hash_destroy(&multi->hostcache);
   Curl_cpool_destroy(&multi->cpool);
+  Curl_cshutdn_destroy(&multi->cshutdn, multi->admin);
   Curl_ssl_scache_destroy(multi->ssl_scache);
+  if(multi->admin) {
+    multi->admin->multi = NULL;
+    Curl_close(&multi->admin);
+  }
+
   free(multi);
   return NULL;
 }
@@ -396,6 +417,15 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *d)
   Curl_cpool_xfer_init(data);
   multi_warn_debug(multi, data);
 
+  /* The admin handle only ever has default timeouts set. To improve the
+     state somewhat we clone the timeouts from each added handle so that the
+     admin handle always has the same timeouts as the most recently added
+     easy handle. */
+  multi->admin->set.timeout = data->set.timeout;
+  multi->admin->set.server_response_timeout =
+    data->set.server_response_timeout;
+  multi->admin->set.no_signal = data->set.no_signal;
+
   CURL_TRC_M(data, "added, transfers=%u", multi->num_easy);
   return CURLM_OK;
 }
@@ -475,7 +505,7 @@ static void multi_done_locked(struct connectdata *conn,
                      conn->bits.close, mdctx->premature,
                      Curl_conn_is_multiplex(conn, FIRSTSOCKET));
     connclose(conn, "disconnecting");
-    Curl_cpool_disconnect(data, conn, mdctx->premature);
+    Curl_conn_terminate(data, conn, mdctx->premature);
   }
   else {
     /* the connection is no longer in use by any transfer */
@@ -684,7 +714,7 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d)
     curl_socket_t s;
     s = Curl_getconnectinfo(data, &c);
     if((s != CURL_SOCKET_BAD) && c) {
-      Curl_cpool_disconnect(data, c, TRUE);
+      Curl_conn_terminate(data, c, TRUE);
     }
   }
 
@@ -1032,7 +1062,8 @@ CURLMcode curl_multi_fdset(CURLM *m,
     }
   }
 
-  Curl_cpool_setfds(&multi->cpool, read_fd_set, write_fd_set, &this_max_fd);
+  Curl_cshutdn_setfds(&multi->cshutdn, multi->admin,
+                      read_fd_set, write_fd_set, &this_max_fd);
 
   *max_fd = this_max_fd;
 
@@ -1068,7 +1099,7 @@ CURLMcode curl_multi_waitfds(CURLM *m,
     need += Curl_waitfds_add_ps(&cwfds, &ps);
   }
 
-  need += Curl_cpool_add_waitfds(&multi->cpool, &cwfds);
+  need += Curl_cshutdn_add_waitfds(&multi->cshutdn, multi->admin, &cwfds);
 
   if(need != cwfds.n && ufds) {
     result = CURLM_OUT_OF_MEMORY;
@@ -1146,7 +1177,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
     }
   }
 
-  if(Curl_cpool_add_pollfds(&multi->cpool, &cpfds)) {
+  if(Curl_cshutdn_add_pollfds(&multi->cshutdn, multi->admin, &cpfds)) {
     result = CURLM_OUT_OF_MEMORY;
     goto out;
   }
@@ -2492,7 +2523,7 @@ statemachine_end:
                We do not have to do this in every case block above where a
                failure is detected */
             Curl_detach_connection(data);
-            Curl_cpool_disconnect(data, conn, dead_connection);
+            Curl_conn_terminate(data, conn, dead_connection);
           }
         }
         else if(data->mstate == MSTATE_CONNECT) {
@@ -2581,7 +2612,7 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles)
        pointer now */
     n = Curl_node_next(e);
 
-    if(data && data != multi->cpool.idata) {
+    if(data && data != multi->admin) {
       /* connection pool handle is processed below */
       sigpipe_apply(data, &pipe_st);
       result = multi_runsingle(multi, &now, data);
@@ -2590,8 +2621,8 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles)
     }
   }
 
-  sigpipe_apply(multi->cpool.idata, &pipe_st);
-  Curl_cpool_multi_perform(multi, CURL_SOCKET_TIMEOUT);
+  sigpipe_apply(multi->admin, &pipe_st);
+  Curl_cshutdn_perform(&multi->cshutdn, multi->admin, CURL_SOCKET_TIMEOUT);
   sigpipe_restore(&pipe_st);
 
   if(multi_ischanged(m, TRUE))
@@ -2690,6 +2721,11 @@ CURLMcode curl_multi_cleanup(CURLM *m)
     }
 
     Curl_cpool_destroy(&multi->cpool);
+    Curl_cshutdn_destroy(&multi->cshutdn, multi->admin);
+    if(multi->admin) {
+      multi->admin->multi = NULL;
+      Curl_close(&multi->admin);
+    }
 
     multi->magic = 0; /* not good anymore */
 
@@ -2855,7 +2891,7 @@ static CURLMcode multi_run_expired(struct multi_run_ctx *mrc)
       continue;
 
     (void)add_next_timeout(mrc->now, multi, data);
-    if(data == multi->cpool.idata) {
+    if(data == multi->admin) {
       mrc->run_cpool = TRUE;
       continue;
     }
@@ -2931,8 +2967,8 @@ static CURLMcode multi_socket(struct Curl_multi *multi,
 
 out:
   if(mrc.run_cpool) {
-    sigpipe_apply(multi->cpool.idata, &mrc.pipe_st);
-    Curl_cpool_multi_perform(multi, s);
+    sigpipe_apply(multi->admin, &mrc.pipe_st);
+    Curl_cshutdn_perform(&multi->cshutdn, multi->admin, s);
   }
   sigpipe_restore(&mrc.pipe_st);
 
index b6efd2fecda90d12d3519dea61c3fec926e72a90..bc28e74754b079a88d940da2eeac2ee8612af8a8 100644 (file)
 #include "llist.h"
 #include "hash.h"
 #include "conncache.h"
+#include "cshutdn.h"
 #include "multi_ev.h"
 #include "psl.h"
 #include "socketpair.h"
 
 struct connectdata;
+struct Curl_easy;
 
 struct Curl_message {
   struct Curl_llist_node list;
@@ -99,6 +101,8 @@ struct Curl_multi {
   struct Curl_llist msgsent; /* in MSGSENT */
   curl_off_t next_easy_mid; /* next multi-id for easy handle added */
 
+  struct Curl_easy *admin; /* internal easy handle for admin operations */
+
   /* callback function and user data pointer for the *socket() API */
   curl_socket_callback socket_cb;
   void *socket_userp;
@@ -140,8 +144,8 @@ struct Curl_multi {
    * the multi handle is cleaned up (see Curl_hash_add2()).*/
   struct Curl_hash proto_hash;
 
-  /* Shared connection cache (bundles)*/
-  struct cpool cpool;
+  struct cshutdn cshutdn; /* connection shutdown handling */
+  struct cpool cpool;     /* connection pool (bundles) */
 
   long max_host_connections; /* if >0, a fixed limit of the maximum number
                                 of connections per host */
index bae6dd273fb4ee4ee29774c8a57be61aa486bc3d..69bf421b750d68c1b072e0a9237ea78223bee6c8 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "urldata.h"
 #include "cfilters.h"
+#include "connect.h"
 #include "sendf.h"
 #include "select.h"
 #include "progress.h"
@@ -74,6 +75,11 @@ timediff_t Curl_pp_state_timeout(struct Curl_easy *data,
     timeout_ms = CURLMIN(timeout_ms, timeout2_ms);
   }
 
+  if(disconnecting) {
+    timediff_t total_left_ms = Curl_timeleft(data, NULL, FALSE);
+    timeout_ms = CURLMIN(timeout_ms, CURLMAX(total_left_ms, 0));
+  }
+
   return timeout_ms;
 }
 
@@ -96,6 +102,7 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data,
     return CURLE_OPERATION_TIMEDOUT; /* already too little time */
   }
 
+  DEBUGF(infof(data, "pp_statematch, timeout=%" FMT_TIMEDIFF_T, timeout_ms));
   if(block) {
     interval_ms = 1000;  /* use 1 second timeout intervals */
     if(timeout_ms < interval_ms)
@@ -135,6 +142,8 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data,
   }
   else if(rc)
     result = pp->statemachine(data, data->conn);
+  else if(disconnecting)
+    return CURLE_OPERATION_TIMEDOUT;
 
   return result;
 }
index 4145e0c6533a37d9105593da2b08438c1ded4cd4..938c9a9d799793cde06d29ef162b864f4c4671f2 100644 (file)
@@ -47,6 +47,16 @@ curl_share_init(void)
     share->magic = CURL_GOOD_SHARE;
     share->specifier |= (1 << CURL_LOCK_DATA_SHARE);
     Curl_init_dnscache(&share->hostcache, 23);
+    share->admin = curl_easy_init();
+    if(!share->admin) {
+      free(share);
+      return NULL;
+    }
+    share->admin->state.internal = TRUE;
+#ifdef DEBUGBUILD
+    if(getenv("CURL_DEBUG"))
+      share->admin->set.verbose = TRUE;
+#endif
   }
 
   return share;
@@ -125,9 +135,9 @@ curl_share_setopt(CURLSH *sh, CURLSHoption option, ...)
 
     case CURL_LOCK_DATA_CONNECT:
       /* It is safe to set this option several times on a share. */
-      if(!share->cpool.idata) {
+      if(!share->cpool.initialised) {
         if(Curl_cpool_init(&share->cpool, Curl_on_disconnect,
-                           NULL, share, 103))
+                           share->admin, share, 103))
           res = CURLSHE_NOMEM;
       }
       break;
@@ -257,6 +267,7 @@ curl_share_cleanup(CURLSH *sh)
 #endif
 
   Curl_psl_destroy(&share->psl);
+  Curl_close(&share->admin);
 
   if(share->unlockfunc)
     share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata);
index d0cdb1b2680b4c14a235093c8c86427ab2402d02..240e2b4a074d124073b9fe2ef251ba8db045cfc8 100644 (file)
@@ -31,6 +31,7 @@
 #include "urldata.h"
 #include "conncache.h"
 
+struct Curl_easy;
 struct Curl_ssl_scache;
 
 #define CURL_GOOD_SHARE 0x7e117a1e
@@ -48,6 +49,7 @@ struct Curl_share {
   curl_lock_function lockfunc;
   curl_unlock_function unlockfunc;
   void *clientdata;
+  struct Curl_easy *admin;
   struct cpool cpool;
   struct Curl_hash hostcache;
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
index a12944cad59d44ad9ca6ff2b7227936c47ef377e..a841cfdb6e62fbf3b045b28ca1296f95f46af95b 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -1209,7 +1209,7 @@ static bool url_match_conn(struct connectdata *conn, void *userdata)
   }
   else if(Curl_conn_seems_dead(conn, data, NULL)) {
     /* removed and disconnect. Do not treat as aborted. */
-    Curl_cpool_disconnect(data, conn, FALSE);
+    Curl_conn_terminate(data, conn, FALSE);
     return FALSE;
   }
 
@@ -3540,14 +3540,12 @@ static CURLcode create_conn(struct Curl_easy *data,
     /* Setup a "faked" transfer that will do nothing */
     if(!result) {
       Curl_attach_connection(data, conn);
-      result = Curl_cpool_add_conn(data, conn);
-      if(result)
-        goto out;
+      result = Curl_cpool_add(data, conn);
+      if(!result) {
+        /* Setup whatever necessary for a resumed transfer */
+        result = setup_range(data);
+      }
 
-      /*
-       * Setup whatever necessary for a resumed transfer
-       */
-      result = setup_range(data);
       if(result) {
         DEBUGASSERT(conn->handler->done);
         /* we ignore the return code for the protocol-specific DONE */
@@ -3684,7 +3682,7 @@ static CURLcode create_conn(struct Curl_easy *data,
       }
 
       Curl_attach_connection(data, conn);
-      result = Curl_cpool_add_conn(data, conn);
+      result = Curl_cpool_add(data, conn);
       if(result)
         goto out;
     }
@@ -3823,7 +3821,7 @@ CURLcode Curl_connect(struct Curl_easy *data,
     /* We are not allowed to return failure with memory left allocated in the
        connectdata struct, free those here */
     Curl_detach_connection(data);
-    Curl_cpool_disconnect(data, conn, TRUE);
+    Curl_conn_terminate(data, conn, TRUE);
   }
 
   return result;
index 2acce8ed0df1ac51ef60b79e28fb0288ec9f8078..e04200d2e7429fad355051cccc4847a76b0b3d43 100644 (file)
@@ -755,6 +755,7 @@ struct ldapconninfo;
  */
 struct connectdata {
   struct Curl_llist_node cpool_node; /* conncache lists */
+  struct Curl_llist_node cshutdn_node; /* cshutdn list */
 
   curl_closesocket_callback fclosesocket; /* function closing the socket(s) */
   void *closesocket_client;
@@ -1135,6 +1136,7 @@ typedef enum {
   EXPIRE_QUIC,
   EXPIRE_FTP_ACCEPT,
   EXPIRE_ALPN_EYEBALLS,
+  EXPIRE_SHUTDOWN,
   EXPIRE_LAST /* not an actual timer, used as a marker only */
 } expire_id;
 
index 5284b907554ad276f5216bb05dcae45e66daad27..2ba9165c119f6ff656e69629c21dc9776eb5a708 100644 (file)
@@ -1853,7 +1853,7 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
     if(cf->cft == &Curl_cft_ssl) {
       bool done;
       CURL_TRC_CF(data, cf, "shutdown and remove SSL, start");
-      Curl_shutdown_start(data, sockindex, NULL);
+      Curl_shutdown_start(data, sockindex, 0, NULL);
       result = vtls_shutdown_blocking(cf, data, send_shutdown, &done);
       Curl_shutdown_clear(data, sockindex);
       if(!result && !done) /* blocking failed? */
index 1c8f787f640147185050b5d3a0f7aab40275a1ea..f67b48721893d5c1ab54481b14af43e37ade20fc 100644 (file)
@@ -2589,6 +2589,10 @@ static int cb_socket(CURL *easy, curl_socket_t s, int action,
   int events = 0;
   (void)easy;
 
+#if DEBUG_UV
+  fprintf(tool_stderr, "parallel_event: cb_socket, fd=%d, action=%x, p=%p\n",
+          (int)s, action, socketp);
+#endif
   switch(action) {
   case CURL_POLL_IN:
   case CURL_POLL_OUT:
@@ -2678,12 +2682,26 @@ static CURLcode parallel_event(struct parastate *s)
     }
   }
 
+  result = s->result;
+
+  /* Make sure to return some kind of error if there was a multi problem */
+  if(s->mcode) {
+    result = (s->mcode == CURLM_OUT_OF_MEMORY) ? CURLE_OUT_OF_MEMORY :
+      /* The other multi errors should never happen, so return
+         something suitably generic */
+      CURLE_BAD_FUNCTION_ARGUMENT;
+  }
+
+  /* We need to cleanup the multi here, since the uv context lives on the
+   * stack and will be gone. multi_cleanup can triggere events! */
+  curl_multi_cleanup(s->multi);
+
 #if DEBUG_UV
   fprintf(tool_stderr, "DONE parallel_event -> %d, mcode=%d, %d running, "
           "%d more\n",
-          s->result, s->mcode, uv.s->still_running, s->more_transfers);
+          result, s->mcode, uv.s->still_running, s->more_transfers);
 #endif
-  return s->result;
+  return result;
 }
 
 #endif
@@ -2787,7 +2805,7 @@ static CURLcode parallel_transfers(struct GlobalConfig *global,
 #ifdef DEBUGBUILD
   if(global->test_event_based)
 #ifdef USE_LIBUV
-    result = parallel_event(s);
+    return parallel_event(s);
 #else
     errorf(global, "Testing --parallel event-based requires libuv");
 #endif
@@ -3228,7 +3246,9 @@ CURLcode operate(struct GlobalConfig *global, int argc, argv_item_t argv[])
           curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
           curl_share_setopt(share, CURLSHOPT_SHARE,
                             CURL_LOCK_DATA_SSL_SESSION);
-          curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
+          /* Running parallel, use the multi connection cache */
+          if(!global->parallel)
+            curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
           curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
           curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
 
index 8dc248b98be03c3cb868d0c646633f10e7cf9677..2f4b6d562982acd055ff148e7435b95c4b4dc9ab 100644 (file)
@@ -67,8 +67,6 @@ run 1: foobar and so on fun!
 -> Mutex lock SHARE
 <- Mutex unlock SHARE
 -> Mutex lock SHARE
--> Mutex lock CONNECT
-<- Mutex unlock CONNECT
 <- Mutex unlock SHARE
 </datacheck>
 </reply>
index 7cdcfd10374c8129d977e4c287bc05f0c5bc2dc7..ea68391357d80748fbb948e67552d2f26cd38834 100644 (file)
@@ -105,7 +105,7 @@ class TestShutdown:
         r = curl.http_download(urls=[url], alpn_proto=proto)
         r.check_response(http_status=200, count=count)
         shutdowns = [line for line in r.trace_lines
-                     if re.match(r'.*\[CPOOL\] shutdown, done=1', line)]
+                     if re.match(r'.*\[SHUTDOWN\] shutdown, done=1', line)]
         assert len(shutdowns) == count, f'{shutdowns}'
 
     # run downloads with CURLOPT_FORBID_REUSE set, meaning *we* close
@@ -128,7 +128,7 @@ class TestShutdown:
         ])
         r.check_exit_code(0)
         shutdowns = [line for line in r.trace_lines
-                     if re.match(r'.*CPOOL\] shutdown, done=1', line)]
+                     if re.match(r'.*SHUTDOWN\] shutdown, done=1', line)]
         assert len(shutdowns) == count, f'{shutdowns}'
 
     # run event-based downloads with CURLOPT_FORBID_REUSE set, meaning *we* close
@@ -153,7 +153,7 @@ class TestShutdown:
         r.check_response(http_status=200, count=count)
         # check that we closed all connections
         closings = [line for line in r.trace_lines
-                    if re.match(r'.*CPOOL\] closing', line)]
+                    if re.match(r'.*SHUTDOWN\] closing', line)]
         assert len(closings) == count, f'{closings}'
         # check that all connection sockets were removed from event
         removes = [line for line in r.trace_lines
@@ -178,5 +178,5 @@ class TestShutdown:
         r.check_response(http_status=200, count=2)
         # check connection cache closings
         shutdowns = [line for line in r.trace_lines
-                     if re.match(r'.*CPOOL\] shutdown, done=1', line)]
+                     if re.match(r'.*SHUTDOWN\] shutdown, done=1', line)]
         assert len(shutdowns) == 1, f'{shutdowns}'