From: Stefan Eissing Date: Thu, 27 Feb 2025 14:47:30 +0000 (+0100) Subject: shutdowns: split shutdown handling from connection pool X-Git-Tag: curl-8_13_0~305 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=df672695e5992ad9b99819e9950de682e243cb48;p=thirdparty%2Fcurl.git shutdowns: split shutdown handling from connection pool 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 --- diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 543b4d4ca6..58ce220e7b 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -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 \ diff --git a/lib/cfilters.c b/lib/cfilters.c index c7f9ab71f8..3521ed6ed2 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -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); diff --git a/lib/conncache.c b/lib/conncache.c index d926eb69e3..1377849181 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -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" @@ -52,24 +53,24 @@ #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); diff --git a/lib/conncache.h b/lib/conncache.h index d12328cd41..ab99c309af 100644 --- a/lib/conncache.h +++ b/lib/conncache.h @@ -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 */ diff --git a/lib/connect.c b/lib/connect.c index 88ebf52431..076e67c579 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -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, diff --git a/lib/connect.h b/lib/connect.h index cbfe294c92..6a6d6d50df 100644 --- a/lib/connect.h +++ b/lib/connect.h @@ -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 index 0000000000..9d4a5ec67a --- /dev/null +++ b/lib/cshutdn.c @@ -0,0 +1,566 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Linus Nielsen Feltzing, + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#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 index 0000000000..202e869838 --- /dev/null +++ b/lib/cshutdn.h @@ -0,0 +1,104 @@ +#ifndef HEADER_CURL_CSHUTDN_H +#define HEADER_CURL_CSHUTDN_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Linus Nielsen Feltzing, + * + * 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 +#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 */ diff --git a/lib/easy.c b/lib/easy.c index 36619d44cc..2665b7ff7f 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -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); } diff --git a/lib/ftp.c b/lib/ftp.c index c50f5f8de3..1dbc56ef69 100644 --- 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; diff --git a/lib/ftp.h b/lib/ftp.h index 3d0af01587..10d62e28c7 100644 --- 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 */ diff --git a/lib/hostip.c b/lib/hostip.c index 60697552f4..99da27403f 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -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; } diff --git a/lib/multi.c b/lib/multi.c index 71bcd5bc10..ae221d4f46 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -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); diff --git a/lib/multihandle.h b/lib/multihandle.h index b6efd2fecd..bc28e74754 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -27,11 +27,13 @@ #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 */ diff --git a/lib/pingpong.c b/lib/pingpong.c index bae6dd273f..69bf421b75 100644 --- a/lib/pingpong.c +++ b/lib/pingpong.c @@ -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; } diff --git a/lib/share.c b/lib/share.c index 4145e0c653..938c9a9d79 100644 --- a/lib/share.c +++ b/lib/share.c @@ -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); diff --git a/lib/share.h b/lib/share.h index d0cdb1b268..240e2b4a07 100644 --- a/lib/share.h +++ b/lib/share.h @@ -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) diff --git a/lib/url.c b/lib/url.c index a12944cad5..a841cfdb6e 100644 --- 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; diff --git a/lib/urldata.h b/lib/urldata.h index 2acce8ed0d..e04200d2e7 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -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; diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 5284b90755..2ba9165c11 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -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? */ diff --git a/src/tool_operate.c b/src/tool_operate.c index 1c8f787f64..f67b487218 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -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); diff --git a/tests/data/test1554 b/tests/data/test1554 index 8dc248b98b..2f4b6d5629 100644 --- a/tests/data/test1554 +++ b/tests/data/test1554 @@ -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 diff --git a/tests/http/test_19_shutdown.py b/tests/http/test_19_shutdown.py index 7cdcfd1037..ea68391357 100644 --- a/tests/http/test_19_shutdown.py +++ b/tests/http/test_19_shutdown.py @@ -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}'