diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 7ae02bfada..cb48733ae7 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -123,7 +123,6 @@ LIB_CFILES = \ cf-socket.c \ cfilters.c \ conncache.c \ - cshutdn.c \ connect.c \ content_encoding.c \ cookie.c \ @@ -260,7 +259,6 @@ 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 3521ed6ed2..c7f9ab71f8 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, 0, &now); + Curl_shutdown_start(data, sockindex, &now); } else { timeout_ms = Curl_shutdown_timeleft(data->conn, sockindex, &now); diff --git a/lib/conncache.c b/lib/conncache.c index 1377849181..d926eb69e3 100644 --- a/lib/conncache.c +++ b/lib/conncache.c @@ -34,7 +34,6 @@ #include "multiif.h" #include "multi_ev.h" #include "sendf.h" -#include "cshutdn.h" #include "conncache.h" #include "http_negotiate.h" #include "http_ntlm.h" @@ -53,24 +52,24 @@ #define CPOOL_IS_LOCKED(c) ((c) && (c)->locked) -#define CPOOL_LOCK(c,d) \ +#define CPOOL_LOCK(c) \ do { \ if((c)) { \ if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ - Curl_share_lock((d), CURL_LOCK_DATA_CONNECT, \ + Curl_share_lock(((c)->idata), CURL_LOCK_DATA_CONNECT, \ CURL_LOCK_ACCESS_SINGLE); \ DEBUGASSERT(!(c)->locked); \ (c)->locked = TRUE; \ } \ } while(0) -#define CPOOL_UNLOCK(c,d) \ +#define CPOOL_UNLOCK(c) \ do { \ if((c)) { \ DEBUGASSERT((c)->locked); \ (c)->locked = FALSE; \ if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ - Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT); \ + Curl_share_unlock((c)->idata, CURL_LOCK_DATA_CONNECT); \ } \ } while(0) @@ -87,6 +86,24 @@ 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) @@ -133,106 +150,55 @@ static void cpool_bundle_free_entry(void *freethis) int Curl_cpool_init(struct cpool *cpool, Curl_cpool_disconnect_cb *disconnect_cb, - struct Curl_easy *idata, + struct Curl_multi *multi, 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; - cpool->idata = idata; + /* 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->disconnect_cb = disconnect_cb; - cpool->share = share; - cpool->initialised = TRUE; + cpool->idata->multi = cpool->multi = multi; + cpool->idata->share = cpool->share = share; + 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 && 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); + 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); } - CPOOL_UNLOCK(cpool, cpool->idata); - sigpipe_restore(&pipe_st); Curl_hash_destroy(&cpool->dest2bundle); + cpool->multi = NULL; } } @@ -255,14 +221,23 @@ void Curl_cpool_xfer_init(struct Curl_easy *data) DEBUGASSERT(cpool); if(cpool) { - CPOOL_LOCK(cpool, data); + CPOOL_LOCK(cpool); /* 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; - CPOOL_UNLOCK(cpool, data); + /* 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); } else { /* We should not get here, but in a non-debug build, do something */ @@ -271,6 +246,13 @@ 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) { @@ -288,6 +270,260 @@ 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) { @@ -352,204 +588,6 @@ 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, @@ -565,7 +603,7 @@ bool Curl_cpool_find(struct Curl_easy *data, if(!cpool) return FALSE; - CPOOL_LOCK(cpool, data); + CPOOL_LOCK(cpool); bundle = Curl_hash_pick(&cpool->dest2bundle, (void *)destination, dest_len); if(bundle) { struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns); @@ -584,10 +622,102 @@ bool Curl_cpool_find(struct Curl_easy *data, if(done_cb) { result = done_cb(result, userdata); } - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool); 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, @@ -596,7 +726,6 @@ static void cpool_discard_conn(struct cpool *cpool, bool done = FALSE; DEBUGASSERT(data); - DEBUGASSERT(!data->conn); DEBUGASSERT(cpool); DEBUGASSERT(!conn->bits.in_cpool); @@ -626,18 +755,47 @@ static void cpool_discard_conn(struct cpool *cpool, done = TRUE; if(!done) { /* Attempt to shutdown the connection right away. */ - Curl_cshutdn_run_once(cpool->idata, conn, &done); + 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 || !data->multi) - Curl_cshutdn_terminate(cpool->idata, conn, FALSE); - else - Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn); + 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)); } -void Curl_conn_terminate(struct Curl_easy *data, - struct connectdata *conn, - bool aborted) +void Curl_cpool_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool aborted) { struct cpool *cpool = cpool_get_instance(data); bool do_lock; @@ -659,7 +817,7 @@ void Curl_conn_terminate(struct Curl_easy *data, * user callback in find. */ do_lock = !CPOOL_IS_LOCKED(cpool); if(do_lock) - CPOOL_LOCK(cpool, data); + CPOOL_LOCK(cpool); if(conn->bits.in_cpool) { cpool_remove_conn(cpool, conn); @@ -676,15 +834,413 @@ void Curl_conn_terminate(struct Curl_easy *data, cpool_discard_conn(&data->multi->cpool, data, conn, aborted); } else { - /* No multi available, terminate */ + /* No multi available. Make a best-effort shutdown + close */ infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); - Curl_cshutdn_terminate(cpool->idata, conn, !aborted); + cpool_close_and_destroy(NULL, conn, data, !aborted); } if(do_lock) - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool); } +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; @@ -696,7 +1252,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_conn_terminate(data, conn, FALSE); + Curl_cpool_disconnect(data, conn, FALSE); return 1; } return 0; /* continue iteration */ @@ -719,7 +1275,7 @@ void Curl_cpool_prune_dead(struct Curl_easy *data) return; rctx.now = Curl_now(); - CPOOL_LOCK(cpool, data); + CPOOL_LOCK(cpool); elapsed = Curl_timediff(rctx.now, cpool->last_cleanup); if(elapsed >= 1000L) { @@ -727,7 +1283,7 @@ void Curl_cpool_prune_dead(struct Curl_easy *data) ; cpool->last_cleanup = rctx.now; } - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool); } static int conn_upkeep(struct Curl_easy *data, @@ -747,9 +1303,9 @@ CURLcode Curl_cpool_upkeep(void *data) if(!cpool) return CURLE_OK; - CPOOL_LOCK(cpool, data); + CPOOL_LOCK(cpool); cpool_foreach(data, cpool, &now, conn_upkeep); - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool); return CURLE_OK; } @@ -780,9 +1336,9 @@ struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, return NULL; fctx.id = conn_id; fctx.conn = NULL; - CPOOL_LOCK(cpool, data); - cpool_foreach(data, cpool, &fctx, cpool_find_conn); - CPOOL_UNLOCK(cpool, data); + CPOOL_LOCK(cpool); + cpool_foreach(cpool->idata, cpool, &fctx, cpool_find_conn); + CPOOL_UNLOCK(cpool); return fctx.conn; } @@ -815,9 +1371,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, data); + CPOOL_LOCK(cpool); cpool_foreach(data, cpool, &dctx, cpool_do_conn); - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool); } void Curl_cpool_do_locked(struct Curl_easy *data, @@ -826,9 +1382,9 @@ void Curl_cpool_do_locked(struct Curl_easy *data, { struct cpool *cpool = cpool_get_instance(data); if(cpool) { - CPOOL_LOCK(cpool, data); + CPOOL_LOCK(cpool); cb(conn, data, cbdata); - CPOOL_UNLOCK(cpool, data); + CPOOL_UNLOCK(cpool); } else cb(conn, data, cbdata); diff --git a/lib/conncache.h b/lib/conncache.h index ab99c309af..d12328cd41 100644 --- a/lib/conncache.h +++ b/lib/conncache.h @@ -35,19 +35,6 @@ 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 @@ -67,11 +54,12 @@ struct cpool { curl_off_t next_connection_id; curl_off_t next_easy_id; struct curltime last_cleanup; - struct Curl_easy *idata; /* internal handle for maintenance */ + 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_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. @@ -79,7 +67,7 @@ struct cpool { */ int Curl_cpool_init(struct cpool *cpool, Curl_cpool_disconnect_cb *disconnect_cb, - struct Curl_easy *idata, + struct Curl_multi *multi, struct Curl_share *share, size_t size); @@ -90,13 +78,14 @@ 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 `data`'s conn pool. */ +/** + * Get the connection with the given id from the transfer's pool. + */ struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, curl_off_t conn_id); -/* Add the connection to the pool. */ -CURLcode Curl_cpool_add(struct Curl_easy *data, - struct connectdata *conn) WARN_UNUSED_RESULT; +CURLcode Curl_cpool_add_conn(struct Curl_easy *data, + struct connectdata *conn) WARN_UNUSED_RESULT; /** * Return if the pool has reached its configured limits for adding @@ -142,6 +131,17 @@ 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,4 +178,22 @@ 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 076e67c579..88ebf52431 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, - int timeout_ms, struct curltime *nowp) + struct curltime *nowp) { struct curltime now; @@ -171,13 +171,8 @@ void Curl_shutdown_start(struct Curl_easy *data, int sockindex, nowp = &now; } data->conn->shutdown.start[sockindex] = *nowp; - 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); + data->conn->shutdown.timeout_ms = (data->set.shutdowntimeout > 0) ? + data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS; } timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex, diff --git a/lib/connect.h b/lib/connect.h index 6a6d6d50df..cbfe294c92 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, - int timeout_ms, struct curltime *nowp); + 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 deleted file mode 100644 index 9d4a5ec67a..0000000000 --- a/lib/cshutdn.c +++ /dev/null @@ -1,566 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * 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 deleted file mode 100644 index 202e869838..0000000000 --- a/lib/cshutdn.h +++ /dev/null @@ -1,104 +0,0 @@ -#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 2665b7ff7f..36619d44cc 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_conn_terminate(data, c, TRUE); + Curl_cpool_disconnect(data, c, TRUE); } DEBUGASSERT(!data->conn); } diff --git a/lib/ftp.c b/lib/ftp.c index 1dbc56ef69..c50f5f8de3 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -587,9 +587,8 @@ static CURLcode ftp_readresp(struct Curl_easy *data, } #endif - /* store the latest code for later retrieval, except during shutdown */ - if(!data->conn->proto.ftpc.shutdown) - data->info.httpcode = code; + /* store the latest code for later retrieval */ + data->info.httpcode = code; if(ftpcode) *ftpcode = code; @@ -3132,8 +3131,6 @@ 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; @@ -4045,7 +4042,6 @@ 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", @@ -4085,7 +4081,6 @@ 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 10d62e28c7..3d0af01587 100644 --- a/lib/ftp.h +++ b/lib/ftp.h @@ -160,7 +160,6 @@ 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 99da27403f..60697552f4 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_conn_terminate(data, conn, TRUE); + Curl_cpool_disconnect(data, conn, TRUE); } return result; } diff --git a/lib/multi.c b/lib/multi.c index ae221d4f46..71bcd5bc10 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -227,23 +227,8 @@ 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->admin, NULL, chashsize)) + multi, NULL, chashsize)) goto error; if(Curl_ssl_scache_create(sesssize, 2, &multi->ssl_scache)) @@ -279,13 +264,7 @@ 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; } @@ -417,15 +396,6 @@ 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; } @@ -505,7 +475,7 @@ static void multi_done_locked(struct connectdata *conn, conn->bits.close, mdctx->premature, Curl_conn_is_multiplex(conn, FIRSTSOCKET)); connclose(conn, "disconnecting"); - Curl_conn_terminate(data, conn, mdctx->premature); + Curl_cpool_disconnect(data, conn, mdctx->premature); } else { /* the connection is no longer in use by any transfer */ @@ -714,7 +684,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_conn_terminate(data, c, TRUE); + Curl_cpool_disconnect(data, c, TRUE); } } @@ -1062,8 +1032,7 @@ CURLMcode curl_multi_fdset(CURLM *m, } } - Curl_cshutdn_setfds(&multi->cshutdn, multi->admin, - read_fd_set, write_fd_set, &this_max_fd); + Curl_cpool_setfds(&multi->cpool, read_fd_set, write_fd_set, &this_max_fd); *max_fd = this_max_fd; @@ -1099,7 +1068,7 @@ CURLMcode curl_multi_waitfds(CURLM *m, need += Curl_waitfds_add_ps(&cwfds, &ps); } - need += Curl_cshutdn_add_waitfds(&multi->cshutdn, multi->admin, &cwfds); + need += Curl_cpool_add_waitfds(&multi->cpool, &cwfds); if(need != cwfds.n && ufds) { result = CURLM_OUT_OF_MEMORY; @@ -1177,7 +1146,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi, } } - if(Curl_cshutdn_add_pollfds(&multi->cshutdn, multi->admin, &cpfds)) { + if(Curl_cpool_add_pollfds(&multi->cpool, &cpfds)) { result = CURLM_OUT_OF_MEMORY; goto out; } @@ -2523,7 +2492,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_conn_terminate(data, conn, dead_connection); + Curl_cpool_disconnect(data, conn, dead_connection); } } else if(data->mstate == MSTATE_CONNECT) { @@ -2612,7 +2581,7 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles) pointer now */ n = Curl_node_next(e); - if(data && data != multi->admin) { + if(data && data != multi->cpool.idata) { /* connection pool handle is processed below */ sigpipe_apply(data, &pipe_st); result = multi_runsingle(multi, &now, data); @@ -2621,8 +2590,8 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles) } } - sigpipe_apply(multi->admin, &pipe_st); - Curl_cshutdn_perform(&multi->cshutdn, multi->admin, CURL_SOCKET_TIMEOUT); + sigpipe_apply(multi->cpool.idata, &pipe_st); + Curl_cpool_multi_perform(multi, CURL_SOCKET_TIMEOUT); sigpipe_restore(&pipe_st); if(multi_ischanged(m, TRUE)) @@ -2721,11 +2690,6 @@ 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 */ @@ -2891,7 +2855,7 @@ static CURLMcode multi_run_expired(struct multi_run_ctx *mrc) continue; (void)add_next_timeout(mrc->now, multi, data); - if(data == multi->admin) { + if(data == multi->cpool.idata) { mrc->run_cpool = TRUE; continue; } @@ -2967,8 +2931,8 @@ static CURLMcode multi_socket(struct Curl_multi *multi, out: if(mrc.run_cpool) { - sigpipe_apply(multi->admin, &mrc.pipe_st); - Curl_cshutdn_perform(&multi->cshutdn, multi->admin, s); + sigpipe_apply(multi->cpool.idata, &mrc.pipe_st); + Curl_cpool_multi_perform(multi, s); } sigpipe_restore(&mrc.pipe_st); diff --git a/lib/multihandle.h b/lib/multihandle.h index bc28e74754..b6efd2fecd 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -27,13 +27,11 @@ #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; @@ -101,8 +99,6 @@ 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; @@ -144,8 +140,8 @@ struct Curl_multi { * the multi handle is cleaned up (see Curl_hash_add2()).*/ struct Curl_hash proto_hash; - struct cshutdn cshutdn; /* connection shutdown handling */ - struct cpool cpool; /* connection pool (bundles) */ + /* Shared connection cache (bundles)*/ + struct cpool cpool; 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 69bf421b75..bae6dd273f 100644 --- a/lib/pingpong.c +++ b/lib/pingpong.c @@ -29,7 +29,6 @@ #include "urldata.h" #include "cfilters.h" -#include "connect.h" #include "sendf.h" #include "select.h" #include "progress.h" @@ -75,11 +74,6 @@ 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; } @@ -102,7 +96,6 @@ 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) @@ -142,8 +135,6 @@ 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 938c9a9d79..4145e0c653 100644 --- a/lib/share.c +++ b/lib/share.c @@ -47,16 +47,6 @@ 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; @@ -135,9 +125,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.initialised) { + if(!share->cpool.idata) { if(Curl_cpool_init(&share->cpool, Curl_on_disconnect, - share->admin, share, 103)) + NULL, share, 103)) res = CURLSHE_NOMEM; } break; @@ -267,7 +257,6 @@ 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 240e2b4a07..d0cdb1b268 100644 --- a/lib/share.h +++ b/lib/share.h @@ -31,7 +31,6 @@ #include "urldata.h" #include "conncache.h" -struct Curl_easy; struct Curl_ssl_scache; #define CURL_GOOD_SHARE 0x7e117a1e @@ -49,7 +48,6 @@ 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 a841cfdb6e..a12944cad5 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_conn_terminate(data, conn, FALSE); + Curl_cpool_disconnect(data, conn, FALSE); return FALSE; } @@ -3540,12 +3540,14 @@ 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(data, conn); - if(!result) { - /* Setup whatever necessary for a resumed transfer */ - result = setup_range(data); - } + result = Curl_cpool_add_conn(data, conn); + if(result) + goto out; + /* + * 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 */ @@ -3682,7 +3684,7 @@ static CURLcode create_conn(struct Curl_easy *data, } Curl_attach_connection(data, conn); - result = Curl_cpool_add(data, conn); + result = Curl_cpool_add_conn(data, conn); if(result) goto out; } @@ -3821,7 +3823,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_conn_terminate(data, conn, TRUE); + Curl_cpool_disconnect(data, conn, TRUE); } return result; diff --git a/lib/urldata.h b/lib/urldata.h index 92b863ee4f..0f6278e201 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -753,7 +753,6 @@ 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; @@ -1134,7 +1133,6 @@ 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 2ba9165c11..5284b90755 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, 0, NULL); + Curl_shutdown_start(data, sockindex, 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 f67b487218..1c8f787f64 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -2589,10 +2589,6 @@ 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: @@ -2682,26 +2678,12 @@ 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", - result, s->mcode, uv.s->still_running, s->more_transfers); + s->result, s->mcode, uv.s->still_running, s->more_transfers); #endif - return result; + return s->result; } #endif @@ -2805,7 +2787,7 @@ static CURLcode parallel_transfers(struct GlobalConfig *global, #ifdef DEBUGBUILD if(global->test_event_based) #ifdef USE_LIBUV - return parallel_event(s); + result = parallel_event(s); #else errorf(global, "Testing --parallel event-based requires libuv"); #endif @@ -3246,9 +3228,7 @@ 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); - /* 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_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 2f4b6d5629..8dc248b98b 100644 --- a/tests/data/test1554 +++ b/tests/data/test1554 @@ -67,6 +67,8 @@ 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 ea68391357..7cdcfd1037 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'.*\[SHUTDOWN\] shutdown, done=1', line)] + if re.match(r'.*\[CPOOL\] 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'.*SHUTDOWN\] shutdown, done=1', line)] + if re.match(r'.*CPOOL\] 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'.*SHUTDOWN\] closing', line)] + if re.match(r'.*CPOOL\] 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'.*SHUTDOWN\] shutdown, done=1', line)] + if re.match(r'.*CPOOL\] shutdown, done=1', line)] assert len(shutdowns) == 1, f'{shutdowns}'