cf-socket.c \
cfilters.c \
conncache.c \
+ cshutdn.c \
connect.c \
content_encoding.c \
cookie.c \
cf-socket.h \
cfilters.h \
conncache.h \
+ cshutdn.h \
connect.h \
content_encoding.h \
cookie.h \
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);
#include "multiif.h"
#include "multi_ev.h"
#include "sendf.h"
+#include "cshutdn.h"
#include "conncache.h"
#include "http_negotiate.h"
#include "http_ntlm.h"
#define CPOOL_IS_LOCKED(c) ((c) && (c)->locked)
-#define CPOOL_LOCK(c) \
+#define CPOOL_LOCK(c,d) \
do { \
if((c)) { \
if(CURL_SHARE_KEEP_CONNECT((c)->share)) \
- Curl_share_lock(((c)->idata), CURL_LOCK_DATA_CONNECT, \
+ Curl_share_lock((d), CURL_LOCK_DATA_CONNECT, \
CURL_LOCK_ACCESS_SINGLE); \
DEBUGASSERT(!(c)->locked); \
(c)->locked = TRUE; \
} \
} while(0)
-#define CPOOL_UNLOCK(c) \
+#define CPOOL_UNLOCK(c,d) \
do { \
if((c)) { \
DEBUGASSERT((c)->locked); \
(c)->locked = FALSE; \
if(CURL_SHARE_KEEP_CONNECT((c)->share)) \
- Curl_share_unlock((c)->idata, CURL_LOCK_DATA_CONNECT); \
+ Curl_share_unlock((d), CURL_LOCK_DATA_CONNECT); \
} \
} while(0)
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)
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;
}
}
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 */
}
}
-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)
{
return bundle;
}
-static void cpool_remove_bundle(struct cpool *cpool,
- struct cpool_bundle *bundle)
+static struct connectdata *
+cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle)
{
- if(!cpool)
- return;
+ struct Curl_llist_node *curr;
+ timediff_t highscore = -1;
+ timediff_t score;
+ struct curltime now;
+ struct connectdata *oldest_idle = NULL;
+ struct connectdata *conn;
- Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len);
+ now = Curl_now();
+ curr = Curl_llist_head(&bundle->conns);
+ while(curr) {
+ conn = Curl_node_elem(curr);
+
+ if(!CONN_INUSE(conn)) {
+ /* Set higher score for the age passed since the connection was used */
+ score = Curl_timediff(now, conn->lastused);
+
+ if(score > highscore) {
+ highscore = score;
+ oldest_idle = conn;
+ }
+ }
+ curr = Curl_node_next(curr);
+ }
+ return oldest_idle;
+}
+
+static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool)
+{
+ struct Curl_hash_iterator iter;
+ struct Curl_llist_node *curr;
+ struct Curl_hash_element *he;
+ struct connectdata *oldest_idle = NULL;
+ struct cpool_bundle *bundle;
+ struct curltime now;
+ timediff_t highscore =- 1;
+ timediff_t score;
+
+ now = Curl_now();
+ Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
+
+ for(he = Curl_hash_next_element(&iter); he;
+ he = Curl_hash_next_element(&iter)) {
+ struct connectdata *conn;
+ bundle = he->ptr;
+
+ for(curr = Curl_llist_head(&bundle->conns); curr;
+ curr = Curl_node_next(curr)) {
+ conn = Curl_node_elem(curr);
+ if(CONN_INUSE(conn) || conn->bits.close || conn->connect_only)
+ continue;
+ /* Set higher score for the age passed since the connection was used */
+ score = Curl_timediff(now, conn->lastused);
+ if(score > highscore) {
+ highscore = score;
+ oldest_idle = conn;
+ }
+ }
+ }
+ return oldest_idle;
}
-static struct connectdata *
-cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle);
int Curl_cpool_check_limits(struct Curl_easy *data,
struct connectdata *conn)
if(!cpool)
return CPOOL_LIMIT_OK;
- if(data && data->multi) {
- dest_limit = data->multi->max_host_connections;
- total_limit = data->multi->max_total_connections;
+ if(cpool->idata->multi) {
+ dest_limit = cpool->idata->multi->max_host_connections;
+ total_limit = cpool->idata->multi->max_total_connections;
}
if(!dest_limit && !total_limit)
return CPOOL_LIMIT_OK;
- CPOOL_LOCK(cpool);
+ CPOOL_LOCK(cpool, cpool->idata);
if(dest_limit) {
size_t live;
bundle = cpool_find_bundle(cpool, conn);
live = bundle ? Curl_llist_count(&bundle->conns) : 0;
- shutdowns = cpool_shutdown_dest_count(cpool, conn->destination);
+ shutdowns = Curl_cshutdn_dest_count(data, conn->destination);
while(!shutdowns && bundle && live >= dest_limit) {
struct connectdata *oldest_idle = NULL;
/* The bundle is full. Extract the oldest connection that may
FMT_OFF_T " from %zu to reach destination "
"limit of %zu", oldest_idle->connection_id,
Curl_llist_count(&bundle->conns), dest_limit);
- Curl_cpool_disconnect(data, oldest_idle, FALSE);
+ Curl_conn_terminate(cpool->idata, oldest_idle, FALSE);
/* in case the bundle was destroyed in disconnect, look it up again */
bundle = cpool_find_bundle(cpool, conn);
live = bundle ? Curl_llist_count(&bundle->conns) : 0;
- shutdowns = cpool_shutdown_dest_count(cpool, conn->destination);
+ shutdowns = Curl_cshutdn_dest_count(cpool->idata, conn->destination);
}
if((live + shutdowns) >= dest_limit) {
result = CPOOL_LIMIT_DEST;
}
if(total_limit) {
- shutdowns = Curl_llist_count(&cpool->shutdowns);
+ shutdowns = Curl_cshutdn_count(cpool->idata);
while((cpool->num_conn + shutdowns) >= total_limit) {
struct connectdata *oldest_idle = cpool_get_oldest_idle(cpool);
if(!oldest_idle)
FMT_OFF_T " from %zu to reach total "
"limit of %zu",
oldest_idle->connection_id, cpool->num_conn, total_limit);
- Curl_cpool_disconnect(data, oldest_idle, FALSE);
- shutdowns = Curl_llist_count(&cpool->shutdowns);
+ Curl_conn_terminate(cpool->idata, oldest_idle, FALSE);
+ shutdowns = Curl_cshutdn_count(cpool->idata);
}
if((cpool->num_conn + shutdowns) >= total_limit) {
result = CPOOL_LIMIT_TOTAL;
}
out:
- CPOOL_UNLOCK(cpool);
+ CPOOL_UNLOCK(cpool, cpool->idata);
return result;
}
-CURLcode Curl_cpool_add_conn(struct Curl_easy *data,
- struct connectdata *conn)
+CURLcode Curl_cpool_add(struct Curl_easy *data,
+ struct connectdata *conn)
{
CURLcode result = CURLE_OK;
struct cpool_bundle *bundle = NULL;
if(!cpool)
return CURLE_FAILED_INIT;
- CPOOL_LOCK(cpool);
+ CPOOL_LOCK(cpool, data);
bundle = cpool_find_bundle(cpool, conn);
if(!bundle) {
bundle = cpool_add_bundle(cpool, conn);
cpool->num_conn++;
DEBUGF(infof(data, "Added connection %" FMT_OFF_T ". "
"The cache now contains %zu members",
- conn->connection_id,
- cpool->num_conn + Curl_llist_count(&cpool->shutdowns)));
+ conn->connection_id, cpool->num_conn));
out:
- CPOOL_UNLOCK(cpool);
+ CPOOL_UNLOCK(cpool, data);
return result;
}
-static void cpool_remove_conn(struct cpool *cpool,
- struct connectdata *conn)
-{
- struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
- DEBUGASSERT(cpool);
- if(list) {
- /* The connection is certainly in the pool, but where? */
- struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn);
- if(bundle && (list == &bundle->conns)) {
- cpool_bundle_remove(bundle, conn);
- if(!Curl_llist_count(&bundle->conns))
- cpool_remove_bundle(cpool, bundle);
- conn->bits.in_cpool = FALSE;
- cpool->num_conn--;
- }
- else {
- /* Not in a bundle, already in the shutdown list? */
- DEBUGASSERT(list == &cpool->shutdowns);
- }
- }
-}
-
/* This function iterates the entire connection pool and calls the function
func() with the connection pointer as the first argument and the supplied
'param' argument as the other.
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.
/* may be called form a callback already under lock */
bool do_lock = !CPOOL_IS_LOCKED(cpool);
if(do_lock)
- CPOOL_LOCK(cpool);
+ CPOOL_LOCK(cpool, data);
if(cpool->num_conn > maxconnects) {
- infof(data, "Connection pool is full, closing the oldest one");
+ infof(data, "Connection pool is full, closing the oldest of %zu/%u",
+ cpool->num_conn, maxconnects);
oldest_idle = cpool_get_oldest_idle(cpool);
kept = (oldest_idle != conn);
if(oldest_idle) {
- Curl_cpool_disconnect(cpool->idata, oldest_idle, FALSE);
+ Curl_conn_terminate(data, oldest_idle, FALSE);
}
}
if(do_lock)
- CPOOL_UNLOCK(cpool);
+ CPOOL_UNLOCK(cpool, data);
}
return kept;
}
-/*
- * This function finds the connection in the connection bundle that has been
- * unused for the longest time.
- */
-static struct connectdata *
-cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle)
-{
- struct Curl_llist_node *curr;
- timediff_t highscore = -1;
- timediff_t score;
- struct curltime now;
- struct connectdata *oldest_idle = NULL;
- struct connectdata *conn;
-
- now = Curl_now();
- curr = Curl_llist_head(&bundle->conns);
- while(curr) {
- conn = Curl_node_elem(curr);
-
- if(!CONN_INUSE(conn)) {
- /* Set higher score for the age passed since the connection was used */
- score = Curl_timediff(now, conn->lastused);
-
- if(score > highscore) {
- highscore = score;
- oldest_idle = conn;
- }
- }
- curr = Curl_node_next(curr);
- }
- return oldest_idle;
-}
-
-static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool)
-{
- struct Curl_hash_iterator iter;
- struct Curl_llist_node *curr;
- struct Curl_hash_element *he;
- struct connectdata *oldest_idle = NULL;
- struct cpool_bundle *bundle;
- struct curltime now;
- timediff_t highscore =- 1;
- timediff_t score;
-
- now = Curl_now();
- Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
-
- for(he = Curl_hash_next_element(&iter); he;
- he = Curl_hash_next_element(&iter)) {
- struct connectdata *conn;
- bundle = he->ptr;
-
- for(curr = Curl_llist_head(&bundle->conns); curr;
- curr = Curl_node_next(curr)) {
- conn = Curl_node_elem(curr);
- if(CONN_INUSE(conn) || conn->bits.close || conn->connect_only)
- continue;
- /* Set higher score for the age passed since the connection was used */
- score = Curl_timediff(now, conn->lastused);
- if(score > highscore) {
- highscore = score;
- oldest_idle = conn;
- }
- }
- }
- return oldest_idle;
-}
-
bool Curl_cpool_find(struct Curl_easy *data,
const char *destination, size_t dest_len,
Curl_cpool_conn_match_cb *conn_cb,
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);
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,
bool done = FALSE;
DEBUGASSERT(data);
+ DEBUGASSERT(!data->conn);
DEBUGASSERT(cpool);
DEBUGASSERT(!conn->bits.in_cpool);
done = TRUE;
if(!done) {
/* Attempt to shutdown the connection right away. */
- Curl_attach_connection(data, conn);
- cpool_run_conn_shutdown(data, conn, &done);
- CURL_TRC_M(data, "[CPOOL] shutdown, done=%d", done);
- Curl_detach_connection(data);
- }
-
- if(done) {
- cpool_close_and_destroy(cpool, conn, data, FALSE);
- return;
+ Curl_cshutdn_run_once(cpool->idata, conn, &done);
}
- /* Add the connection to our shutdown list for non-blocking shutdown
- * during multi processing. */
- if(data->multi && data->multi->max_total_connections > 0 &&
- (data->multi->max_total_connections <=
- (long)(cpool->num_conn + Curl_llist_count(&cpool->shutdowns)))) {
- CURL_TRC_M(data, "[CPOOL] discarding oldest shutdown connection "
- "due to connection limit of %ld",
- data->multi->max_total_connections);
- cpool_shutdown_destroy_oldest(cpool);
- }
-
- if(data->multi && data->multi->socket_cb) {
- DEBUGASSERT(cpool == &data->multi->cpool);
- if(cpool_update_shutdown_ev(cpool, data->multi, conn)) {
- CURL_TRC_M(data, "[CPOOL] update events failed, discarding #%"
- FMT_OFF_T, conn->connection_id);
- cpool_close_and_destroy(cpool, conn, data, FALSE);
- return;
- }
- }
-
- Curl_llist_append(&cpool->shutdowns, conn, &conn->cpool_node);
- CURL_TRC_M(data, "[CPOOL] added #%" FMT_OFF_T
- " to shutdowns, now %zu conns in shutdown",
- conn->connection_id, Curl_llist_count(&cpool->shutdowns));
+ if(done || !data->multi)
+ Curl_cshutdn_terminate(cpool->idata, conn, FALSE);
+ else
+ Curl_cshutdn_add(&data->multi->cshutdn, conn, cpool->num_conn);
}
-void Curl_cpool_disconnect(struct Curl_easy *data,
- struct connectdata *conn,
- bool aborted)
+void Curl_conn_terminate(struct Curl_easy *data,
+ struct connectdata *conn,
+ bool aborted)
{
struct cpool *cpool = cpool_get_instance(data);
bool do_lock;
* 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);
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;
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 */
return;
rctx.now = Curl_now();
- CPOOL_LOCK(cpool);
+ CPOOL_LOCK(cpool, data);
elapsed = Curl_timediff(rctx.now, cpool->last_cleanup);
if(elapsed >= 1000L) {
;
cpool->last_cleanup = rctx.now;
}
- CPOOL_UNLOCK(cpool);
+ CPOOL_UNLOCK(cpool, data);
}
static int conn_upkeep(struct Curl_easy *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;
}
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;
}
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,
{
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);
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
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.
*/
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);
* 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
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.
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 */
}
void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
- struct curltime *nowp)
+ int timeout_ms, struct curltime *nowp)
{
struct curltime now;
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,
#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. */
--- /dev/null
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#include <curl/curl.h>
+
+#include "urldata.h"
+#include "url.h"
+#include "cfilters.h"
+#include "progress.h"
+#include "multiif.h"
+#include "multi_ev.h"
+#include "sendf.h"
+#include "cshutdn.h"
+#include "http_negotiate.h"
+#include "http_ntlm.h"
+#include "sigpipe.h"
+#include "connect.h"
+#include "select.h"
+#include "strcase.h"
+#include "strparse.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+static void cshutdn_run_conn_handler(struct Curl_easy *data,
+ struct connectdata *conn)
+{
+ if(!conn->bits.shutdown_handler) {
+ if(conn->dns_entry)
+ Curl_resolv_unlink(data, &conn->dns_entry);
+
+ /* Cleanup NTLM connection-related data */
+ Curl_http_auth_cleanup_ntlm(conn);
+
+ /* Cleanup NEGOTIATE connection-related data */
+ Curl_http_auth_cleanup_negotiate(conn);
+
+ if(conn->handler && conn->handler->disconnect) {
+ /* Some disconnect handlers do a blocking wait on server responses.
+ * FTP/IMAP/SMTP and SFTP are among them. When using the internal
+ * handle, set an overall short timeout so we do not hang for the
+ * default 120 seconds. */
+ if(data->state.internal) {
+ data->set.timeout = DEFAULT_SHUTDOWN_TIMEOUT_MS;
+ (void)Curl_pgrsTime(data, TIMER_STARTOP);
+ }
+
+ /* This is set if protocol-specific cleanups should be made */
+ DEBUGF(infof(data, "connection #%" FMT_OFF_T
+ ", shutdown protocol handler (aborted=%d)",
+ conn->connection_id, conn->bits.aborted));
+ /* There are protocol handlers that block on retrieving
+ * server responses here (FTP). Set a short timeout. */
+ conn->handler->disconnect(data, conn, conn->bits.aborted);
+ }
+
+ /* possible left-overs from the async name resolvers */
+ Curl_resolver_cancel(data);
+
+ conn->bits.shutdown_handler = TRUE;
+ }
+}
+
+static void cshutdn_run_once(struct Curl_easy *data,
+ struct connectdata *conn,
+ bool *done)
+{
+ CURLcode r1, r2;
+ bool done1, done2;
+
+ /* We expect to be attached when called */
+ DEBUGASSERT(data->conn == conn);
+
+ cshutdn_run_conn_handler(data, conn);
+
+ if(conn->bits.shutdown_filters) {
+ *done = TRUE;
+ return;
+ }
+
+ if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET))
+ r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1);
+ else {
+ r1 = CURLE_OK;
+ done1 = TRUE;
+ }
+
+ if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET))
+ r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2);
+ else {
+ r2 = CURLE_OK;
+ done2 = TRUE;
+ }
+
+ /* we are done when any failed or both report success */
+ *done = (r1 || r2 || (done1 && done2));
+ if(*done)
+ conn->bits.shutdown_filters = TRUE;
+}
+
+void Curl_cshutdn_run_once(struct Curl_easy *data,
+ struct connectdata *conn,
+ bool *done)
+{
+ DEBUGASSERT(!data->conn);
+ Curl_attach_connection(data, conn);
+ cshutdn_run_once(data, conn, done);
+ CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done);
+ Curl_detach_connection(data);
+}
+
+
+void Curl_cshutdn_terminate(struct Curl_easy *data,
+ struct connectdata *conn,
+ bool do_shutdown)
+{
+ struct Curl_easy *admin = data;
+ bool done;
+
+ /* there must be a connection to close */
+ DEBUGASSERT(conn);
+ /* it must be removed from the connection pool */
+ DEBUGASSERT(!conn->bits.in_cpool);
+ /* the transfer must be detached from the connection */
+ DEBUGASSERT(data && !data->conn);
+
+ /* If we can obtain an internal admin handle, use that to attach
+ * and terminate the connection. Some protocol will try to mess with
+ * `data` during shutdown and we do not want that with a `data` from
+ * the application. */
+ if(data->multi && data->multi->admin)
+ admin = data->multi->admin;
+
+ Curl_attach_connection(admin, conn);
+
+ cshutdn_run_conn_handler(admin, conn);
+ if(do_shutdown) {
+ /* Make a last attempt to shutdown handlers and filters, if
+ * not done so already. */
+ cshutdn_run_once(admin, conn, &done);
+ }
+ CURL_TRC_M(admin, "[SHUTDOWN] closing connection");
+ Curl_conn_close(admin, SECONDARYSOCKET);
+ Curl_conn_close(admin, FIRSTSOCKET);
+ Curl_detach_connection(admin);
+
+ if(data->multi)
+ Curl_multi_ev_conn_done(data->multi, data, conn);
+ Curl_conn_free(admin, conn);
+
+ if(data->multi) {
+ CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged");
+ Curl_multi_connchanged(data->multi);
+ }
+}
+
+static void cshutdn_destroy_oldest(struct cshutdn *cshutdn,
+ struct Curl_easy *data)
+{
+ struct Curl_llist_node *e;
+ struct connectdata *conn;
+
+ e = Curl_llist_head(&cshutdn->list);
+ if(e) {
+ SIGPIPE_VARIABLE(pipe_st);
+ conn = Curl_node_elem(e);
+ Curl_node_remove(e);
+ sigpipe_init(&pipe_st);
+ sigpipe_apply(data, &pipe_st);
+ Curl_cshutdn_terminate(data, conn, FALSE);
+ sigpipe_restore(&pipe_st);
+ }
+}
+
+#define NUM_POLLS_ON_STACK 10
+
+static CURLcode cshutdn_wait(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ int timeout_ms)
+{
+ struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK];
+ struct curl_pollfds cpfds;
+ CURLcode result;
+
+ Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK);
+
+ result = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds);
+ if(result)
+ goto out;
+
+ Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000));
+
+out:
+ Curl_pollfds_cleanup(&cpfds);
+ return result;
+}
+
+
+static void cshutdn_perform(struct cshutdn *cshutdn,
+ struct Curl_easy *data)
+{
+ struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list);
+ struct Curl_llist_node *enext;
+ struct connectdata *conn;
+ struct curltime *nowp = NULL;
+ struct curltime now;
+ timediff_t next_expire_ms = 0, ms;
+ bool done;
+
+ if(!e)
+ return;
+
+ CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections",
+ Curl_llist_count(&cshutdn->list));
+ while(e) {
+ enext = Curl_node_next(e);
+ conn = Curl_node_elem(e);
+ Curl_cshutdn_run_once(data, conn, &done);
+ if(done) {
+ Curl_node_remove(e);
+ Curl_cshutdn_terminate(data, conn, FALSE);
+ }
+ else {
+ /* idata has one timer list, but maybe more than one connection.
+ * Set EXPIRE_SHUTDOWN to the smallest time left for all. */
+ if(!nowp) {
+ now = Curl_now();
+ nowp = &now;
+ }
+ ms = Curl_conn_shutdown_timeleft(conn, nowp);
+ if(ms && ms < next_expire_ms)
+ next_expire_ms = ms;
+ }
+ e = enext;
+ }
+
+ if(next_expire_ms)
+ Curl_expire_ex(data, nowp, next_expire_ms, EXPIRE_SHUTDOWN);
+}
+
+
+static void cshutdn_terminate_all(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ int timeout_ms)
+{
+ struct curltime started = Curl_now();
+ struct Curl_llist_node *e;
+ SIGPIPE_VARIABLE(pipe_st);
+
+ DEBUGASSERT(cshutdn);
+ DEBUGASSERT(data);
+
+ CURL_TRC_M(data, "[SHUTDOWN] shutdown all");
+ sigpipe_init(&pipe_st);
+ sigpipe_apply(data, &pipe_st);
+
+ while(Curl_llist_head(&cshutdn->list)) {
+ timediff_t timespent;
+ int remain_ms;
+
+ cshutdn_perform(cshutdn, data);
+
+ if(!Curl_llist_head(&cshutdn->list)) {
+ CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly");
+ break;
+ }
+
+ /* wait for activity, timeout or "nothing" */
+ timespent = Curl_timediff(Curl_now(), started);
+ if(timespent >= (timediff_t)timeout_ms) {
+ CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s",
+ (timeout_ms > 0) ? "timeout" : "best effort done");
+ break;
+ }
+
+ remain_ms = timeout_ms - (int)timespent;
+ if(cshutdn_wait(cshutdn, data, remain_ms)) {
+ CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted");
+ break;
+ }
+ }
+
+ /* Terminate any remaining. */
+ e = Curl_llist_head(&cshutdn->list);
+ while(e) {
+ struct connectdata *conn = Curl_node_elem(e);
+ Curl_node_remove(e);
+ Curl_cshutdn_terminate(data, conn, FALSE);
+ e = Curl_llist_head(&cshutdn->list);
+ }
+ DEBUGASSERT(!Curl_llist_count(&cshutdn->list));
+
+ Curl_hostcache_clean(data, data->dns.hostcache);
+
+ sigpipe_restore(&pipe_st);
+}
+
+
+int Curl_cshutdn_init(struct cshutdn *cshutdn,
+ struct Curl_multi *multi)
+{
+ DEBUGASSERT(multi);
+ cshutdn->multi = multi;
+ Curl_llist_init(&cshutdn->list, NULL);
+ cshutdn->initialised = TRUE;
+ return 0; /* good */
+}
+
+
+void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
+ struct Curl_easy *data)
+{
+ if(cshutdn->initialised && data) {
+ int timeout_ms = 0;
+ /* Just for testing, run graceful shutdown */
+#ifdef DEBUGBUILD
+ {
+ const char *p = getenv("CURL_GRACEFUL_SHUTDOWN");
+ if(p) {
+ curl_off_t l;
+ if(!Curl_str_number(&p, &l, INT_MAX))
+ timeout_ms = (int)l;
+ }
+ }
+#endif
+
+ CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms",
+ Curl_llist_count(&cshutdn->list), timeout_ms);
+ cshutdn_terminate_all(cshutdn, data, timeout_ms);
+ }
+ cshutdn->multi = NULL;
+}
+
+size_t Curl_cshutdn_count(struct Curl_easy *data)
+{
+ if(data && data->multi) {
+ struct cshutdn *csd = &data->multi->cshutdn;
+ return Curl_llist_count(&csd->list);
+ }
+ return 0;
+}
+
+size_t Curl_cshutdn_dest_count(struct Curl_easy *data,
+ const char *destination)
+{
+ if(data && data->multi) {
+ struct cshutdn *csd = &data->multi->cshutdn;
+ size_t n = 0;
+ struct Curl_llist_node *e = Curl_llist_head(&csd->list);
+ while(e) {
+ struct connectdata *conn = Curl_node_elem(e);
+ if(!strcmp(destination, conn->destination))
+ ++n;
+ e = Curl_node_next(e);
+ }
+ return n;
+ }
+ return 0;
+}
+
+
+static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ struct connectdata *conn)
+{
+ CURLMcode mresult;
+
+ DEBUGASSERT(cshutdn);
+ DEBUGASSERT(cshutdn->multi->socket_cb);
+
+ Curl_attach_connection(data, conn);
+ mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn);
+ Curl_detach_connection(data);
+ return mresult;
+}
+
+
+void Curl_cshutdn_add(struct cshutdn *cshutdn,
+ struct connectdata *conn,
+ size_t conns_in_pool)
+{
+ struct Curl_easy *data = cshutdn->multi->admin;
+ size_t max_total = (cshutdn->multi->max_total_connections > 0) ?
+ (size_t)cshutdn->multi->max_total_connections : 0;
+
+ /* Add the connection to our shutdown list for non-blocking shutdown
+ * during multi processing. */
+ if(max_total > 0 && (max_total <=
+ (conns_in_pool + Curl_llist_count(&cshutdn->list)))) {
+ CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection "
+ "due to connection limit of %zu", max_total);
+ cshutdn_destroy_oldest(cshutdn, data);
+ }
+
+ if(cshutdn->multi->socket_cb) {
+ if(cshutdn_update_ev(cshutdn, data, conn)) {
+ CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%"
+ FMT_OFF_T, conn->connection_id);
+ Curl_cshutdn_terminate(data, conn, FALSE);
+ return;
+ }
+ }
+
+ Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node);
+ CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T
+ " to shutdowns, now %zu conns in shutdown",
+ conn->connection_id, Curl_llist_count(&cshutdn->list));
+}
+
+
+static void cshutdn_multi_socket(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ curl_socket_t s)
+{
+ struct Curl_llist_node *e;
+ struct connectdata *conn;
+ bool done;
+
+ DEBUGASSERT(cshutdn->multi->socket_cb);
+ e = Curl_llist_head(&cshutdn->list);
+ while(e) {
+ conn = Curl_node_elem(e);
+ if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) {
+ Curl_cshutdn_run_once(data, conn, &done);
+ if(done || cshutdn_update_ev(cshutdn, data, conn)) {
+ Curl_node_remove(e);
+ Curl_cshutdn_terminate(data, conn, FALSE);
+ }
+ break;
+ }
+ e = Curl_node_next(e);
+ }
+}
+
+
+void Curl_cshutdn_perform(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ curl_socket_t s)
+{
+ if((s == CURL_SOCKET_TIMEOUT) || (!cshutdn->multi->socket_cb))
+ cshutdn_perform(cshutdn, data);
+ else
+ cshutdn_multi_socket(cshutdn, data, s);
+}
+
+/* return fd_set info about the shutdown connections */
+void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ fd_set *read_fd_set, fd_set *write_fd_set,
+ int *maxfd)
+{
+ if(Curl_llist_head(&cshutdn->list)) {
+ struct Curl_llist_node *e;
+
+ for(e = Curl_llist_head(&cshutdn->list); e;
+ e = Curl_node_next(e)) {
+ struct easy_pollset ps;
+ unsigned int i;
+ struct connectdata *conn = Curl_node_elem(e);
+ memset(&ps, 0, sizeof(ps));
+ Curl_attach_connection(data, conn);
+ Curl_conn_adjust_pollset(data, conn, &ps);
+ Curl_detach_connection(data);
+
+ for(i = 0; i < ps.num; i++) {
+#if defined(__DJGPP__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warith-conversion"
+#endif
+ if(ps.actions[i] & CURL_POLL_IN)
+ FD_SET(ps.sockets[i], read_fd_set);
+ if(ps.actions[i] & CURL_POLL_OUT)
+ FD_SET(ps.sockets[i], write_fd_set);
+#if defined(__DJGPP__)
+#pragma GCC diagnostic pop
+#endif
+ if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) &&
+ ((int)ps.sockets[i] > *maxfd))
+ *maxfd = (int)ps.sockets[i];
+ }
+ }
+ }
+}
+
+/* return information about the shutdown connections */
+unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ struct Curl_waitfds *cwfds)
+{
+ unsigned int need = 0;
+
+ if(Curl_llist_head(&cshutdn->list)) {
+ struct Curl_llist_node *e;
+ struct easy_pollset ps;
+ struct connectdata *conn;
+
+ for(e = Curl_llist_head(&cshutdn->list); e;
+ e = Curl_node_next(e)) {
+ conn = Curl_node_elem(e);
+ memset(&ps, 0, sizeof(ps));
+ Curl_attach_connection(data, conn);
+ Curl_conn_adjust_pollset(data, conn, &ps);
+ Curl_detach_connection(data);
+
+ need += Curl_waitfds_add_ps(cwfds, &ps);
+ }
+ }
+ return need;
+}
+
+CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ struct curl_pollfds *cpfds)
+{
+ CURLcode result = CURLE_OK;
+
+ if(Curl_llist_head(&cshutdn->list)) {
+ struct Curl_llist_node *e;
+ struct easy_pollset ps;
+ struct connectdata *conn;
+
+ for(e = Curl_llist_head(&cshutdn->list); e;
+ e = Curl_node_next(e)) {
+ conn = Curl_node_elem(e);
+ memset(&ps, 0, sizeof(ps));
+ Curl_attach_connection(data, conn);
+ Curl_conn_adjust_pollset(data, conn, &ps);
+ Curl_detach_connection(data);
+
+ result = Curl_pollfds_add_ps(cpfds, &ps);
+ if(result) {
+ Curl_pollfds_cleanup(cpfds);
+ goto out;
+ }
+ }
+ }
+out:
+ return result;
+}
--- /dev/null
+#ifndef HEADER_CURL_CSHUTDN_H
+#define HEADER_CURL_CSHUTDN_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include <curl/curl.h>
+#include "timeval.h"
+
+struct connectdata;
+struct Curl_easy;
+struct curl_pollfds;
+struct Curl_waitfds;
+struct Curl_multi;
+struct Curl_share;
+
+/* Run the shutdown of the connection once.
+ * Will shortly attach/detach `data` to `conn` while doing so.
+ * `done` will be set TRUE if any error was encountered or if
+ * the connection was shut down completely. */
+void Curl_cshutdn_run_once(struct Curl_easy *data,
+ struct connectdata *conn,
+ bool *done);
+
+/* Terminates the connection, e.g. closes and destroys it.
+ * If `run_shutdown` is TRUE, the shutdown will be run once before
+ * terminating it.
+ * Takes ownership of `conn`. */
+void Curl_cshutdn_terminate(struct Curl_easy *data,
+ struct connectdata *conn,
+ bool run_shutdown);
+
+/* A `cshutdown` is always owned by a multi handle to maintain
+ * the connections to be shut down. It registers timers and
+ * sockets to monitor via the multi handle. */
+struct cshutdn {
+ struct Curl_llist list; /* connections being shut down */
+ struct Curl_multi *multi; /* the multi owning this */
+ BIT(initialised);
+};
+
+/* Init as part of the given multi handle. */
+int Curl_cshutdn_init(struct cshutdn *cshutdn,
+ struct Curl_multi *multi);
+
+/* Terminate all remaining connections and free resources. */
+void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
+ struct Curl_easy *data);
+
+/* Number of connections being shut down. */
+size_t Curl_cshutdn_count(struct Curl_easy *data);
+
+/* Number of connections to the destination being shut down. */
+size_t Curl_cshutdn_dest_count(struct Curl_easy *data,
+ const char *destination);
+
+/* Add a connection to have it shut down. Will terminate the oldest
+ * connection when total connection limit of multi is being reached. */
+void Curl_cshutdn_add(struct cshutdn *cshutdn,
+ struct connectdata *conn,
+ size_t conns_in_pool);
+
+/* Add sockets and POLLIN/OUT flags for connections being shut down. */
+CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ struct curl_pollfds *cpfds);
+
+unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ struct Curl_waitfds *cwfds);
+
+void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ fd_set *read_fd_set, fd_set *write_fd_set,
+ int *maxfd);
+
+/* Run shut down connections using socket. If socket is CURL_SOCKET_TIMEOUT,
+ * run maintenance on all connections. */
+void Curl_cshutdn_perform(struct cshutdn *cshutdn,
+ struct Curl_easy *data,
+ curl_socket_t s);
+
+#endif /* HEADER_CURL_CSHUTDN_H */
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);
}
}
#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;
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;
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",
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;
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 */
if(result) {
Curl_detach_connection(data);
- Curl_cpool_disconnect(data, conn, TRUE);
+ Curl_conn_terminate(data, conn, TRUE);
}
return result;
}
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))
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;
}
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;
}
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 */
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);
}
}
}
}
- 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;
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;
}
}
- 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;
}
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) {
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);
}
}
- 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))
}
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 */
continue;
(void)add_next_timeout(mrc->now, multi, data);
- if(data == multi->cpool.idata) {
+ if(data == multi->admin) {
mrc->run_cpool = TRUE;
continue;
}
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);
#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;
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;
* 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 */
#include "urldata.h"
#include "cfilters.h"
+#include "connect.h"
#include "sendf.h"
#include "select.h"
#include "progress.h"
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;
}
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)
}
else if(rc)
result = pp->statemachine(data, data->conn);
+ else if(disconnecting)
+ return CURLE_OPERATION_TIMEDOUT;
return result;
}
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;
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;
#endif
Curl_psl_destroy(&share->psl);
+ Curl_close(&share->admin);
if(share->unlockfunc)
share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata);
#include "urldata.h"
#include "conncache.h"
+struct Curl_easy;
struct Curl_ssl_scache;
#define CURL_GOOD_SHARE 0x7e117a1e
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)
}
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;
}
/* 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 */
}
Curl_attach_connection(data, conn);
- result = Curl_cpool_add_conn(data, conn);
+ result = Curl_cpool_add(data, conn);
if(result)
goto out;
}
/* 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;
*/
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;
EXPIRE_QUIC,
EXPIRE_FTP_ACCEPT,
EXPIRE_ALPN_EYEBALLS,
+ EXPIRE_SHUTDOWN,
EXPIRE_LAST /* not an actual timer, used as a marker only */
} expire_id;
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? */
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:
}
}
+ 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
#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
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);
-> Mutex lock SHARE
<- Mutex unlock SHARE
-> Mutex lock SHARE
--> Mutex lock CONNECT
-<- Mutex unlock CONNECT
<- Mutex unlock SHARE
</datacheck>
</reply>
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
])
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
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
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}'