diff --git a/lib/Makefile.inc b/lib/Makefile.inc index cb48733ae7..7ae02bfada 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 \ @@ -259,6 +260,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,260 +288,6 @@ cpool_add_bundle(struct cpool *cpool, struct connectdata *conn) return bundle; } -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 struct connectdata * -cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle); - -int Curl_cpool_check_limits(struct Curl_easy *data, - struct connectdata *conn) -{ - struct cpool *cpool = cpool_get_instance(data); - struct cpool_bundle *bundle; - size_t dest_limit = 0; - size_t total_limit = 0; - size_t shutdowns; - int result = CPOOL_LIMIT_OK; - - 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(!dest_limit && !total_limit) - return CPOOL_LIMIT_OK; - - CPOOL_LOCK(cpool); - 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); - while(!shutdowns && bundle && live >= dest_limit) { - struct connectdata *oldest_idle = NULL; - /* The bundle is full. Extract the oldest connection that may - * be removed now, if there is one. */ - oldest_idle = cpool_bundle_get_oldest_idle(bundle); - if(!oldest_idle) - break; - /* disconnect the old conn and continue */ - CURL_TRC_M(data, "Discarding connection #%" - 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); - - /* 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); - } - if((live + shutdowns) >= dest_limit) { - result = CPOOL_LIMIT_DEST; - goto out; - } - } - - if(total_limit) { - shutdowns = Curl_llist_count(&cpool->shutdowns); - while((cpool->num_conn + shutdowns) >= total_limit) { - struct connectdata *oldest_idle = cpool_get_oldest_idle(cpool); - if(!oldest_idle) - break; - /* disconnect the old conn and continue */ - CURL_TRC_M(data, "Discarding connection #%" - 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); - } - if((cpool->num_conn + shutdowns) >= total_limit) { - result = CPOOL_LIMIT_TOTAL; - goto out; - } - } - -out: - CPOOL_UNLOCK(cpool); - return result; -} - -CURLcode Curl_cpool_add_conn(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct cpool_bundle *bundle = NULL; - struct cpool *cpool = cpool_get_instance(data); - DEBUGASSERT(conn); - - DEBUGASSERT(cpool); - if(!cpool) - return CURLE_FAILED_INIT; - - CPOOL_LOCK(cpool); - bundle = cpool_find_bundle(cpool, conn); - if(!bundle) { - bundle = cpool_add_bundle(cpool, conn); - if(!bundle) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - } - - cpool_bundle_add(bundle, conn); - conn->connection_id = cpool->next_connection_id++; - 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))); -out: - CPOOL_UNLOCK(cpool); - - 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. - - The cpool lock is still held when the callback is called. It needs it, - so that it can safely continue traversing the lists once the callback - returns. - - Returns TRUE if the loop was aborted due to the callback's return code. - - Return 0 from func() to continue the loop, return 1 to abort it. - */ -static bool cpool_foreach(struct Curl_easy *data, - struct cpool *cpool, - void *param, - int (*func)(struct Curl_easy *data, - struct connectdata *conn, void *param)) -{ - struct Curl_hash_iterator iter; - struct Curl_hash_element *he; - - if(!cpool) - return FALSE; - - Curl_hash_start_iterate(&cpool->dest2bundle, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - struct Curl_llist_node *curr; - struct cpool_bundle *bundle = he->ptr; - he = Curl_hash_next_element(&iter); - - curr = Curl_llist_head(&bundle->conns); - while(curr) { - /* Yes, we need to update curr before calling func(), because func() - might decide to remove the connection */ - struct connectdata *conn = Curl_node_elem(curr); - curr = Curl_node_next(curr); - - if(1 == func(data, conn, param)) { - return TRUE; - } - } - } - 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. - * - * Return TRUE if idle connection kept in pool, FALSE if closed. - */ -bool Curl_cpool_conn_now_idle(struct Curl_easy *data, - struct connectdata *conn) -{ - unsigned int maxconnects = !data->multi->maxconnects ? - data->multi->num_easy * 4 : data->multi->maxconnects; - struct connectdata *oldest_idle = NULL; - struct cpool *cpool = cpool_get_instance(data); - bool kept = TRUE; - - conn->lastused = Curl_now(); /* it was used up until now */ - if(cpool && maxconnects) { - /* may be called form a callback already under lock */ - bool do_lock = !CPOOL_IS_LOCKED(cpool); - if(do_lock) - CPOOL_LOCK(cpool); - if(cpool->num_conn > maxconnects) { - infof(data, "Connection pool is full, closing the oldest one"); - - oldest_idle = cpool_get_oldest_idle(cpool); - kept = (oldest_idle != conn); - if(oldest_idle) { - Curl_cpool_disconnect(cpool->idata, oldest_idle, FALSE); - } - } - if(do_lock) - CPOOL_UNLOCK(cpool); - } - - 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) { @@ -588,6 +352,204 @@ static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool) return oldest_idle; } + +int Curl_cpool_check_limits(struct Curl_easy *data, + struct connectdata *conn) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cpool_bundle *bundle; + size_t dest_limit = 0; + size_t total_limit = 0; + size_t shutdowns; + int result = CPOOL_LIMIT_OK; + + if(!cpool) + return CPOOL_LIMIT_OK; + + 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->idata); + if(dest_limit) { + size_t live; + + bundle = cpool_find_bundle(cpool, conn); + live = bundle ? Curl_llist_count(&bundle->conns) : 0; + 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 + * be removed now, if there is one. */ + oldest_idle = cpool_bundle_get_oldest_idle(bundle); + if(!oldest_idle) + break; + /* disconnect the old conn and continue */ + CURL_TRC_M(data, "Discarding connection #%" + FMT_OFF_T " from %zu to reach destination " + "limit of %zu", oldest_idle->connection_id, + Curl_llist_count(&bundle->conns), dest_limit); + 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 = Curl_cshutdn_dest_count(cpool->idata, conn->destination); + } + if((live + shutdowns) >= dest_limit) { + result = CPOOL_LIMIT_DEST; + goto out; + } + } + + if(total_limit) { + 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) + break; + /* disconnect the old conn and continue */ + CURL_TRC_M(data, "Discarding connection #%" + FMT_OFF_T " from %zu to reach total " + "limit of %zu", + oldest_idle->connection_id, cpool->num_conn, total_limit); + 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; + goto out; + } + } + +out: + CPOOL_UNLOCK(cpool, cpool->idata); + return result; +} + +CURLcode Curl_cpool_add(struct Curl_easy *data, + struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct cpool_bundle *bundle = NULL; + struct cpool *cpool = cpool_get_instance(data); + DEBUGASSERT(conn); + + DEBUGASSERT(cpool); + if(!cpool) + return CURLE_FAILED_INIT; + + CPOOL_LOCK(cpool, data); + bundle = cpool_find_bundle(cpool, conn); + if(!bundle) { + bundle = cpool_add_bundle(cpool, conn); + if(!bundle) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + + cpool_bundle_add(bundle, conn); + conn->connection_id = cpool->next_connection_id++; + cpool->num_conn++; + DEBUGF(infof(data, "Added connection %" FMT_OFF_T ". " + "The cache now contains %zu members", + conn->connection_id, cpool->num_conn)); +out: + CPOOL_UNLOCK(cpool, data); + + return result; +} + +/* 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. + + The cpool lock is still held when the callback is called. It needs it, + so that it can safely continue traversing the lists once the callback + returns. + + Returns TRUE if the loop was aborted due to the callback's return code. + + Return 0 from func() to continue the loop, return 1 to abort it. + */ +static bool cpool_foreach(struct Curl_easy *data, + struct cpool *cpool, + void *param, + int (*func)(struct Curl_easy *data, + struct connectdata *conn, void *param)) +{ + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + + if(!cpool) + return FALSE; + + Curl_hash_start_iterate(&cpool->dest2bundle, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + struct Curl_llist_node *curr; + struct cpool_bundle *bundle = he->ptr; + he = Curl_hash_next_element(&iter); + + curr = Curl_llist_head(&bundle->conns); + while(curr) { + /* Yes, we need to update curr before calling func(), because func() + might decide to remove the connection */ + struct connectdata *conn = Curl_node_elem(curr); + curr = Curl_node_next(curr); + + if(1 == func(data, conn, param)) { + return TRUE; + } + } + } + return FALSE; +} + +/* + * A connection (already in the pool) has become idle. Do any + * cleanups in regard to the pool's limits. + * + * Return TRUE if idle connection kept in pool, FALSE if closed. + */ +bool Curl_cpool_conn_now_idle(struct Curl_easy *data, + struct connectdata *conn) +{ + unsigned int maxconnects = !data->multi->maxconnects ? + data->multi->num_easy * 4 : data->multi->maxconnects; + struct connectdata *oldest_idle = NULL; + struct cpool *cpool = cpool_get_instance(data); + bool kept = TRUE; + + conn->lastused = Curl_now(); /* it was used up until now */ + if(cpool && maxconnects) { + /* may be called form a callback already under lock */ + bool do_lock = !CPOOL_IS_LOCKED(cpool); + if(do_lock) + CPOOL_LOCK(cpool, data); + if(cpool->num_conn > maxconnects) { + 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_conn_terminate(data, oldest_idle, FALSE); + } + } + if(do_lock) + CPOOL_UNLOCK(cpool, data); + } + + return kept; +} + 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); + Curl_cshutdn_run_once(cpool->idata, conn, &done); } - if(done) { - cpool_close_and_destroy(cpool, conn, data, FALSE); - return; - } - - /* 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 0f6278e201..92b863ee4f 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -753,6 +753,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; @@ -1133,6 +1134,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}'