summaryrefslogtreecommitdiff
path: root/libs/libcurl/src/multi.c
diff options
context:
space:
mode:
authordartraiden <wowemuh@gmail.com>2024-09-11 21:49:43 +0300
committerdartraiden <wowemuh@gmail.com>2024-09-11 21:52:35 +0300
commitc36db68a5f58ec6c9135eff778b6e77c30e58088 (patch)
tree22eea126db82c50388c9f94cd683d6ec6f04e6cb /libs/libcurl/src/multi.c
parent02bb82c1c0a4031e7b2f5578370fa5acd620f8e1 (diff)
libcurl: update to 8.10.0
Diffstat (limited to 'libs/libcurl/src/multi.c')
-rw-r--r--libs/libcurl/src/multi.c1092
1 files changed, 533 insertions, 559 deletions
diff --git a/libs/libcurl/src/multi.c b/libs/libcurl/src/multi.c
index 39f4c2c4f2..051bbd7efa 100644
--- a/libs/libcurl/src/multi.c
+++ b/libs/libcurl/src/multi.c
@@ -94,9 +94,12 @@ static CURLMcode add_next_timeout(struct curltime now,
struct Curl_multi *multi,
struct Curl_easy *d);
static CURLMcode multi_timeout(struct Curl_multi *multi,
+ struct curltime *expire_time,
long *timeout_ms);
static void process_pending_handles(struct Curl_multi *multi);
static void multi_xfer_bufs_free(struct Curl_multi *multi);
+static void Curl_expire_ex(struct Curl_easy *data, const struct curltime *nowp,
+ timediff_t milli, expire_id id);
#ifdef DEBUGBUILD
static const char * const multi_statename[]={
@@ -247,10 +250,8 @@ static size_t trhash(void *key, size_t key_length, size_t slots_num)
static size_t trhash_compare(void *k1, size_t k1_len, void *k2, size_t k2_len)
{
- (void)k1_len;
(void)k2_len;
-
- return *(struct Curl_easy **)k1 == *(struct Curl_easy **)k2;
+ return !memcmp(k1, k2, k1_len);
}
static void trhash_dtor(void *nada)
@@ -410,15 +411,18 @@ struct Curl_multi *Curl_multi_handle(size_t hashsize, /* socket hash */
Curl_hash_init(&multi->proto_hash, 23,
Curl_hash_str, Curl_str_key_compare, ph_freeentry);
- if(Curl_conncache_init(&multi->conn_cache, multi, chashsize))
+ if(Curl_cpool_init(&multi->cpool, Curl_on_disconnect,
+ multi, NULL, chashsize))
goto error;
Curl_llist_init(&multi->msglist, NULL);
+ Curl_llist_init(&multi->process, NULL);
Curl_llist_init(&multi->pending, NULL);
Curl_llist_init(&multi->msgsent, NULL);
multi->multiplexing = TRUE;
multi->max_concurrent_streams = 100;
+ multi->last_timeout_ms = -1;
#ifdef USE_WINSOCK
multi->wsa_event = WSACreateEvent();
@@ -440,7 +444,7 @@ error:
sockhash_destroy(&multi->sockhash);
Curl_hash_destroy(&multi->proto_hash);
Curl_hash_destroy(&multi->hostcache);
- Curl_conncache_destroy(&multi->conn_cache);
+ Curl_cpool_destroy(&multi->cpool);
free(multi);
return NULL;
}
@@ -466,52 +470,6 @@ static void multi_warn_debug(struct Curl_multi *multi, struct Curl_easy *data)
#define multi_warn_debug(x,y) Curl_nop_stmt
#endif
-/* returns TRUE if the easy handle is supposed to be present in the main link
- list */
-static bool in_main_list(struct Curl_easy *data)
-{
- return ((data->mstate != MSTATE_PENDING) &&
- (data->mstate != MSTATE_MSGSENT));
-}
-
-static void link_easy(struct Curl_multi *multi,
- struct Curl_easy *data)
-{
- /* We add the new easy entry last in the list. */
- data->next = NULL; /* end of the line */
- if(multi->easyp) {
- struct Curl_easy *last = multi->easylp;
- last->next = data;
- data->prev = last;
- multi->easylp = data; /* the new last node */
- }
- else {
- /* first node, make prev NULL! */
- data->prev = NULL;
- multi->easylp = multi->easyp = data; /* both first and last */
- }
-}
-
-/* unlink the given easy handle from the linked list of easy handles */
-static void unlink_easy(struct Curl_multi *multi,
- struct Curl_easy *data)
-{
- /* make the previous node point to our next */
- if(data->prev)
- data->prev->next = data->next;
- else
- multi->easyp = data->next; /* point to first node */
-
- /* make our next point to our previous node */
- if(data->next)
- data->next->prev = data->prev;
- else
- multi->easylp = data->prev; /* point to last node */
-
- data->prev = data->next = NULL;
-}
-
-
CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
struct Curl_easy *data)
{
@@ -554,7 +512,7 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
/*
* No failure allowed in this function beyond this point. No modification of
* easy nor multi handle allowed before this except for potential multi's
- * connection cache growing which will not be undone in this function no
+ * connection pool growing which will not be undone in this function no
* matter what.
*/
if(data->set.errorbuffer)
@@ -574,21 +532,11 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
happen. */
Curl_expire(data, 0, EXPIRE_RUN_NOW);
- /* A somewhat crude work-around for a little glitch in Curl_update_timer()
- that happens if the lastcall time is set to the same time when the handle
- is removed as when the next handle is added, as then the check in
- Curl_update_timer() that prevents calling the application multiple times
- with the same timer info will not trigger and then the new handle's
- timeout will not be notified to the app.
-
- The work-around is thus simply to clear the 'lastcall' variable to force
- Curl_update_timer() to always trigger a callback to the app when a new
- easy handle is added */
- memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall));
-
rc = Curl_update_timer(multi);
- if(rc)
+ if(rc) {
+ data->multi = NULL; /* not anymore */
return rc;
+ }
/* set the easy handle */
multistate(data, MSTATE_INIT);
@@ -601,13 +549,6 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
data->dns.hostcachetype = HCACHE_MULTI;
}
- /* Point to the shared or multi handle connection cache */
- if(data->share && (data->share->specifier & (1<< CURL_LOCK_DATA_CONNECT)))
- data->state.conn_cache = &data->share->conn_cache;
- else
- data->state.conn_cache = &multi->conn_cache;
- data->state.lastconnect_id = -1;
-
#ifdef USE_LIBPSL
/* Do the same for PSL. */
if(data->share && (data->share->specifier & (1 << CURL_LOCK_DATA_PSL)))
@@ -616,7 +557,8 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
data->psl = &multi->psl;
#endif
- link_easy(multi, data);
+ /* add the easy handle to the process list */
+ Curl_llist_append(&multi->process, data, &data->multi_queue);
/* increase the node-counter */
multi->num_easy++;
@@ -624,21 +566,12 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
/* increase the alive-counter */
multi->num_alive++;
- CONNCACHE_LOCK(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. */
- data->state.conn_cache->closure_handle->set.timeout = data->set.timeout;
- data->state.conn_cache->closure_handle->set.server_response_timeout =
- data->set.server_response_timeout;
- data->state.conn_cache->closure_handle->set.no_signal =
- data->set.no_signal;
- data->id = data->state.conn_cache->next_easy_id++;
- if(data->state.conn_cache->next_easy_id <= 0)
- data->state.conn_cache->next_easy_id = 0;
- CONNCACHE_UNLOCK(data);
+ /* the identifier inside the multi instance */
+ data->mid = multi->next_easy_mid++;
+ if(multi->next_easy_mid <= 0)
+ multi->next_easy_mid = 0;
+ Curl_cpool_xfer_init(data);
multi_warn_debug(multi, data);
return CURLM_OK;
@@ -660,6 +593,91 @@ static void debug_print_sock_hash(void *p)
}
#endif
+struct multi_done_ctx {
+ BIT(premature);
+};
+
+static void multi_done_locked(struct connectdata *conn,
+ struct Curl_easy *data,
+ void *userdata)
+{
+ struct multi_done_ctx *mdctx = userdata;
+
+ Curl_detach_connection(data);
+
+ if(CONN_INUSE(conn)) {
+ /* Stop if still used. */
+ DEBUGF(infof(data, "Connection still in use %zu, "
+ "no more multi_done now!",
+ Curl_llist_count(&conn->easyq)));
+ return;
+ }
+
+ data->state.done = TRUE; /* called just now! */
+ data->state.recent_conn_id = conn->connection_id;
+
+ if(conn->dns_entry)
+ Curl_resolv_unlink(data, &conn->dns_entry); /* done with this */
+ Curl_hostcache_prune(data);
+
+ /* if data->set.reuse_forbid is TRUE, it means the libcurl client has
+ forced us to close this connection. This is ignored for requests taking
+ place in a NTLM/NEGOTIATE authentication handshake
+
+ if conn->bits.close is TRUE, it means that the connection should be
+ closed in spite of all our efforts to be nice, due to protocol
+ restrictions in our or the server's end
+
+ if premature is TRUE, it means this connection was said to be DONE before
+ the entire request operation is complete and thus we cannot know in what
+ state it is for reusing, so we are forced to close it. In a perfect world
+ we can add code that keep track of if we really must close it here or not,
+ but currently we have no such detail knowledge.
+ */
+
+ if((data->set.reuse_forbid
+#if defined(USE_NTLM)
+ && !(conn->http_ntlm_state == NTLMSTATE_TYPE2 ||
+ conn->proxy_ntlm_state == NTLMSTATE_TYPE2)
+#endif
+#if defined(USE_SPNEGO)
+ && !(conn->http_negotiate_state == GSS_AUTHRECV ||
+ conn->proxy_negotiate_state == GSS_AUTHRECV)
+#endif
+ ) || conn->bits.close
+ || (mdctx->premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) {
+ DEBUGF(infof(data, "multi_done, not reusing connection=%"
+ FMT_OFF_T ", forbid=%d"
+ ", close=%d, premature=%d, conn_multiplex=%d",
+ conn->connection_id, data->set.reuse_forbid,
+ conn->bits.close, mdctx->premature,
+ Curl_conn_is_multiplex(conn, FIRSTSOCKET)));
+ connclose(conn, "disconnecting");
+ Curl_cpool_disconnect(data, conn, mdctx->premature);
+ }
+ else {
+ /* the connection is no longer in use by any transfer */
+ if(Curl_cpool_conn_now_idle(data, conn)) {
+ /* connection kept in the cpool */
+ const char *host =
+#ifndef CURL_DISABLE_PROXY
+ conn->bits.socksproxy ?
+ conn->socks_proxy.host.dispname :
+ conn->bits.httpproxy ? conn->http_proxy.host.dispname :
+#endif
+ conn->bits.conn_to_host ? conn->conn_to_host.dispname :
+ conn->host.dispname;
+ data->state.lastconnect_id = conn->connection_id;
+ infof(data, "Connection #%" FMT_OFF_T " to host %s left intact",
+ conn->connection_id, host);
+ }
+ else {
+ /* connection was removed from the cpool and destroyed. */
+ data->state.lastconnect_id = -1;
+ }
+ }
+}
+
static CURLcode multi_done(struct Curl_easy *data,
CURLcode status, /* an error if this is called
after an error was detected */
@@ -667,6 +685,9 @@ static CURLcode multi_done(struct Curl_easy *data,
{
CURLcode result, r2;
struct connectdata *conn = data->conn;
+ struct multi_done_ctx mdctx;
+
+ memset(&mdctx, 0, sizeof(mdctx));
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
DEBUGF(infof(data, "multi_done[%s]: status: %d prem: %d done: %d",
@@ -729,106 +750,22 @@ static CURLcode multi_done(struct Curl_easy *data,
if(!result)
result = Curl_req_done(&data->req, data, premature);
- CONNCACHE_LOCK(data);
- Curl_detach_connection(data);
- if(CONN_INUSE(conn)) {
- /* Stop if still used. */
- CONNCACHE_UNLOCK(data);
- DEBUGF(infof(data, "Connection still in use %zu, "
- "no more multi_done now!",
- conn->easyq.size));
- return CURLE_OK;
- }
-
- data->state.done = TRUE; /* called just now! */
-
- if(conn->dns_entry) {
- Curl_resolv_unlock(data, conn->dns_entry); /* done with this */
- conn->dns_entry = NULL;
- }
- Curl_hostcache_prune(data);
-
- /* if data->set.reuse_forbid is TRUE, it means the libcurl client has
- forced us to close this connection. This is ignored for requests taking
- place in a NTLM/NEGOTIATE authentication handshake
-
- if conn->bits.close is TRUE, it means that the connection should be
- closed in spite of all our efforts to be nice, due to protocol
- restrictions in our or the server's end
-
- if premature is TRUE, it means this connection was said to be DONE before
- the entire request operation is complete and thus we cannot know in what
- state it is for reusing, so we are forced to close it. In a perfect world
- we can add code that keep track of if we really must close it here or not,
- but currently we have no such detail knowledge.
- */
-
- data->state.recent_conn_id = conn->connection_id;
- if((data->set.reuse_forbid
-#if defined(USE_NTLM)
- && !(conn->http_ntlm_state == NTLMSTATE_TYPE2 ||
- conn->proxy_ntlm_state == NTLMSTATE_TYPE2)
-#endif
-#if defined(USE_SPNEGO)
- && !(conn->http_negotiate_state == GSS_AUTHRECV ||
- conn->proxy_negotiate_state == GSS_AUTHRECV)
-#endif
- ) || conn->bits.close
- || (premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) {
- DEBUGF(infof(data, "multi_done, not reusing connection=%"
- CURL_FORMAT_CURL_OFF_T ", forbid=%d"
- ", close=%d, premature=%d, conn_multiplex=%d",
- conn->connection_id,
- data->set.reuse_forbid, conn->bits.close, premature,
- Curl_conn_is_multiplex(conn, FIRSTSOCKET)));
- connclose(conn, "disconnecting");
- Curl_conncache_remove_conn(data, conn, FALSE);
- CONNCACHE_UNLOCK(data);
- Curl_disconnect(data, conn, premature);
- }
- else {
- char buffer[256];
- const char *host =
-#ifndef CURL_DISABLE_PROXY
- conn->bits.socksproxy ?
- conn->socks_proxy.host.dispname :
- conn->bits.httpproxy ? conn->http_proxy.host.dispname :
-#endif
- conn->bits.conn_to_host ? conn->conn_to_host.dispname :
- conn->host.dispname;
- /* create string before returning the connection */
- curl_off_t connection_id = conn->connection_id;
- msnprintf(buffer, sizeof(buffer),
- "Connection #%" CURL_FORMAT_CURL_OFF_T " to host %s left intact",
- connection_id, host);
- /* the connection is no longer in use by this transfer */
- CONNCACHE_UNLOCK(data);
- if(Curl_conncache_return_conn(data, conn)) {
- /* remember the most recently used connection */
- data->state.lastconnect_id = connection_id;
- data->state.recent_conn_id = connection_id;
- infof(data, "%s", buffer);
- }
- else
- data->state.lastconnect_id = -1;
- }
+ /* Under the potential connection pool's share lock, decide what to
+ * do with the transfer's connection. */
+ mdctx.premature = premature;
+ Curl_cpool_do_locked(data, data->conn, multi_done_locked, &mdctx);
return result;
}
-static int close_connect_only(struct Curl_easy *data,
- struct connectdata *conn, void *param)
+static void close_connect_only(struct connectdata *conn,
+ struct Curl_easy *data,
+ void *userdata)
{
- (void)param;
- if(data->state.lastconnect_id != conn->connection_id)
- return 0;
-
- if(!conn->connect_only)
- return 1;
-
- connclose(conn, "Removing connect-only easy handle");
-
- return 1;
+ (void)userdata;
+ (void)data;
+ if(conn->connect_only)
+ connclose(conn, "Removing connect-only easy handle");
}
CURLMcode curl_multi_remove_handle(struct Curl_multi *multi,
@@ -836,15 +773,16 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi,
{
struct Curl_easy *easy = data;
bool premature;
- struct Curl_llist_element *e;
+ struct Curl_llist_node *e;
CURLMcode rc;
+ bool removed_timer = FALSE;
/* First, make some basic checks that the CURLM handle is a good handle */
if(!GOOD_MULTI_HANDLE(multi))
return CURLM_BAD_HANDLE;
/* Verify that we got a somewhat good easy handle too */
- if(!GOOD_EASY_HANDLE(data))
+ if(!GOOD_EASY_HANDLE(data) || !multi->num_easy)
return CURLM_BAD_EASY_HANDLE;
/* Prevent users from trying to remove same easy handle more than once */
@@ -888,18 +826,10 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi,
/* The timer must be shut down before data->multi is set to NULL, else the
timenode will remain in the splay tree after curl_easy_cleanup is
called. Do it after multi_done() in case that sets another time! */
- Curl_expire_clear(data);
+ removed_timer = Curl_expire_clear(data);
- if(data->connect_queue.ptr) {
- /* the handle is in the pending or msgsent lists, so go ahead and remove
- it */
- if(data->mstate == MSTATE_PENDING)
- Curl_llist_remove(&multi->pending, &data->connect_queue, NULL);
- else
- Curl_llist_remove(&multi->msgsent, &data->connect_queue, NULL);
- }
- if(in_main_list(data))
- unlink_easy(multi, data);
+ /* the handle is in a list, remove it from whichever it is */
+ Curl_node_remove(&data->multi_queue);
if(data->dns.hostcachetype == HCACHE_MULTI) {
/* stop using the multi handle's DNS cache, *after* the possible
@@ -935,15 +865,14 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi,
curl_socket_t s;
s = Curl_getconnectinfo(data, &c);
if((s != CURL_SOCKET_BAD) && c) {
- Curl_conncache_remove_conn(data, c, TRUE);
- Curl_disconnect(data, c, TRUE);
+ Curl_cpool_disconnect(data, c, TRUE);
}
}
if(data->state.lastconnect_id != -1) {
/* Mark any connect-only connection for closure */
- Curl_conncache_foreach(data, data->state.conn_cache,
- NULL, close_connect_only);
+ Curl_cpool_do_by_id(data, data->state.lastconnect_id,
+ close_connect_only, NULL);
}
#ifdef USE_LIBPSL
@@ -952,33 +881,31 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi,
data->psl = NULL;
#endif
- /* as this was using a shared connection cache we clear the pointer to that
- since we are not part of that multi handle anymore */
- data->state.conn_cache = NULL;
-
- data->multi = NULL; /* clear the association to this multi handle */
-
/* make sure there is no pending message in the queue sent from this easy
handle */
- for(e = multi->msglist.head; e; e = e->next) {
- struct Curl_message *msg = e->ptr;
+ for(e = Curl_llist_head(&multi->msglist); e; e = Curl_node_next(e)) {
+ struct Curl_message *msg = Curl_node_elem(e);
if(msg->extmsg.easy_handle == easy) {
- Curl_llist_remove(&multi->msglist, e, NULL);
+ Curl_node_remove(e);
/* there can only be one from this specific handle */
break;
}
}
+ data->multi = NULL; /* clear the association to this multi handle */
+ data->mid = -1;
+
/* NOTE NOTE NOTE
We do not touch the easy handle here! */
multi->num_easy--; /* one less to care about now */
-
process_pending_handles(multi);
- rc = Curl_update_timer(multi);
- if(rc)
- return rc;
+ if(removed_timer) {
+ rc = Curl_update_timer(multi);
+ if(rc)
+ return rc;
+ }
return CURLM_OK;
}
@@ -999,7 +926,7 @@ void Curl_detach_connection(struct Curl_easy *data)
struct connectdata *conn = data->conn;
if(conn) {
Curl_conn_ev_data_detach(conn, data);
- Curl_llist_remove(&conn->easyq, &data->conn_queue, NULL);
+ Curl_node_remove(&data->conn_queue);
}
data->conn = NULL;
}
@@ -1103,7 +1030,7 @@ static int perform_getsock(struct Curl_easy *data, curl_socket_t *sock)
sock[sockindex] = conn->sockfd;
}
- if(CURL_WANT_SEND(data)) {
+ if(Curl_req_want_send(data)) {
if((conn->sockfd != conn->writesockfd) ||
bitmap == GETSOCK_BLANK) {
/* only if they are not the same socket and we have a readable
@@ -1212,10 +1139,8 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi,
/* Scan through all the easy handles to get the file descriptors set.
Some easy handles may not have connected to the remote host yet,
and then we must make sure that is done. */
- struct Curl_easy *data;
int this_max_fd = -1;
- struct easy_pollset ps;
- unsigned int i;
+ struct Curl_llist_node *e;
(void)exc_fd_set; /* not used */
if(!GOOD_MULTI_HANDLE(multi))
@@ -1224,20 +1149,22 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi,
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
- memset(&ps, 0, sizeof(ps));
- for(data = multi->easyp; data; data = data->next) {
- multi_getsock(data, &ps);
+ for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) {
+ struct Curl_easy *data = Curl_node_elem(e);
+ unsigned int i;
+
+ multi_getsock(data, &data->last_poll);
- for(i = 0; i < ps.num; i++) {
- if(!FDSET_SOCK(ps.sockets[i]))
+ for(i = 0; i < data->last_poll.num; i++) {
+ if(!FDSET_SOCK(data->last_poll.sockets[i]))
/* pretend it does not exist */
continue;
- 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((int)ps.sockets[i] > this_max_fd)
- this_max_fd = (int)ps.sockets[i];
+ if(data->last_poll.actions[i] & CURL_POLL_IN)
+ FD_SET(data->last_poll.sockets[i], read_fd_set);
+ if(data->last_poll.actions[i] & CURL_POLL_OUT)
+ FD_SET(data->last_poll.sockets[i], write_fd_set);
+ if((int)data->last_poll.sockets[i] > this_max_fd)
+ this_max_fd = (int)data->last_poll.sockets[i];
}
}
@@ -1251,10 +1178,9 @@ CURLMcode curl_multi_waitfds(struct Curl_multi *multi,
unsigned int size,
unsigned int *fd_count)
{
- struct Curl_easy *data;
struct curl_waitfds cwfds;
- struct easy_pollset ps;
CURLMcode result = CURLM_OK;
+ struct Curl_llist_node *e;
if(!ufds)
return CURLM_BAD_FUNCTION_ARGUMENT;
@@ -1266,16 +1192,16 @@ CURLMcode curl_multi_waitfds(struct Curl_multi *multi,
return CURLM_RECURSIVE_API_CALL;
Curl_waitfds_init(&cwfds, ufds, size);
- memset(&ps, 0, sizeof(ps));
- for(data = multi->easyp; data; data = data->next) {
- multi_getsock(data, &ps);
- if(Curl_waitfds_add_ps(&cwfds, &ps)) {
+ for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) {
+ struct Curl_easy *data = Curl_node_elem(e);
+ multi_getsock(data, &data->last_poll);
+ if(Curl_waitfds_add_ps(&cwfds, &data->last_poll)) {
result = CURLM_OUT_OF_MEMORY;
goto out;
}
}
- if(Curl_conncache_add_waitfds(&multi->conn_cache, &cwfds)) {
+ if(Curl_cpool_add_waitfds(&multi->cpool, &cwfds)) {
result = CURLM_OUT_OF_MEMORY;
goto out;
}
@@ -1312,15 +1238,16 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
bool extrawait, /* when no socket, wait */
bool use_wakeup)
{
- struct Curl_easy *data;
- struct easy_pollset ps;
size_t i;
+ struct curltime expire_time;
long timeout_internal;
int retcode = 0;
struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK];
struct curl_pollfds cpfds;
unsigned int curl_nfds = 0; /* how many pfds are for curl transfers */
CURLMcode result = CURLM_OK;
+ struct Curl_llist_node *e;
+
#ifdef USE_WINSOCK
WSANETWORKEVENTS wsa_events;
DEBUGASSERT(multi->wsa_event != WSA_INVALID_EVENT);
@@ -1339,18 +1266,19 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
return CURLM_BAD_FUNCTION_ARGUMENT;
Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK);
- memset(&ps, 0, sizeof(ps));
/* Add the curl handles to our pollfds first */
- for(data = multi->easyp; data; data = data->next) {
- multi_getsock(data, &ps);
- if(Curl_pollfds_add_ps(&cpfds, &ps)) {
+ for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) {
+ struct Curl_easy *data = Curl_node_elem(e);
+
+ multi_getsock(data, &data->last_poll);
+ if(Curl_pollfds_add_ps(&cpfds, &data->last_poll)) {
result = CURLM_OUT_OF_MEMORY;
goto out;
}
}
- if(Curl_conncache_add_pollfds(&multi->conn_cache, &cpfds)) {
+ if(Curl_cpool_add_pollfds(&multi->cpool, &cpfds)) {
result = CURLM_OUT_OF_MEMORY;
goto out;
}
@@ -1407,7 +1335,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
* poll. Collecting the sockets may install new timers by protocols
* and connection filters.
* Use the shorter one of the internal and the caller requested timeout. */
- (void)multi_timeout(multi, &timeout_internal);
+ (void)multi_timeout(multi, &expire_time, &timeout_internal);
if((timeout_internal >= 0) && (timeout_internal < (long)timeout_ms))
timeout_ms = (int)timeout_internal;
@@ -1418,7 +1346,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
#endif
int pollrc;
#ifdef USE_WINSOCK
- if(cpfds.n) /* just pre-check with WinSock */
+ if(cpfds.n) /* just pre-check with Winsock */
pollrc = Curl_poll(cpfds.pfds, cpfds.n, 0);
else
pollrc = 0;
@@ -1438,7 +1366,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
WSAWaitForMultipleEvents(1, &multi->wsa_event, FALSE, (DWORD)timeout_ms,
FALSE);
}
- /* With WinSock, we have to run the following section unconditionally
+ /* With Winsock, we have to run the following section unconditionally
to call WSAEventSelect(fd, event, 0) on all the sockets */
{
#endif
@@ -1480,18 +1408,18 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
/* Count up all our own sockets that had activity,
and remove them from the event. */
if(curl_nfds) {
+ for(e = Curl_llist_head(&multi->process); e && !result;
+ e = Curl_node_next(e)) {
+ struct Curl_easy *data = Curl_node_elem(e);
- for(data = multi->easyp; data; data = data->next) {
- multi_getsock(data, &ps);
-
- for(i = 0; i < ps.num; i++) {
+ for(i = 0; i < data->last_poll.num; i++) {
wsa_events.lNetworkEvents = 0;
- if(WSAEnumNetworkEvents(ps.sockets[i], NULL,
+ if(WSAEnumNetworkEvents(data->last_poll.sockets[i], NULL,
&wsa_events) == 0) {
if(ret && !pollrc && wsa_events.lNetworkEvents)
retcode++;
}
- WSAEventSelect(ps.sockets[i], multi->wsa_event, 0);
+ WSAEventSelect(data->last_poll.sockets[i], multi->wsa_event, 0);
}
}
}
@@ -1741,23 +1669,23 @@ static bool multi_handle_timeout(struct Curl_easy *data,
else
since = data->progress.t_startop;
if(data->mstate == MSTATE_RESOLVING)
- failf(data, "Resolving timed out after %" CURL_FORMAT_TIMEDIFF_T
+ failf(data, "Resolving timed out after %" FMT_TIMEDIFF_T
" milliseconds", Curl_timediff(*now, since));
else if(data->mstate == MSTATE_CONNECTING)
- failf(data, "Connection timed out after %" CURL_FORMAT_TIMEDIFF_T
+ failf(data, "Connection timed out after %" FMT_TIMEDIFF_T
" milliseconds", Curl_timediff(*now, since));
else {
struct SingleRequest *k = &data->req;
if(k->size != -1) {
- failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T
- " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %"
- CURL_FORMAT_CURL_OFF_T " bytes received",
+ failf(data, "Operation timed out after %" FMT_TIMEDIFF_T
+ " milliseconds with %" FMT_OFF_T " out of %"
+ FMT_OFF_T " bytes received",
Curl_timediff(*now, since), k->bytecount, k->size);
}
else {
- failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T
- " milliseconds with %" CURL_FORMAT_CURL_OFF_T
- " bytes received", Curl_timediff(*now, since), k->bytecount);
+ failf(data, "Operation timed out after %" FMT_TIMEDIFF_T
+ " milliseconds with %" FMT_OFF_T " bytes received",
+ Curl_timediff(*now, since), k->bytecount);
}
}
*result = CURLE_OPERATION_TIMEDOUT;
@@ -1974,11 +1902,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* There was no connection available. We will go to the pending
state and wait for an available connection. */
multistate(data, MSTATE_PENDING);
-
- /* add this handle to the list of connect-pending handles */
- Curl_llist_append(&multi->pending, data, &data->connect_queue);
- /* unlink from the main list */
- unlink_easy(multi, data);
+ /* unlink from process list */
+ Curl_node_remove(&data->multi_queue);
+ /* add handle to pending list */
+ Curl_llist_append(&multi->pending, data, &data->multi_queue);
result = CURLE_OK;
break;
}
@@ -1996,8 +1923,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
WAITDO or DO! */
rc = CURLM_CALL_MULTI_PERFORM;
- if(connected)
+ if(connected) {
+ if(!data->conn->bits.reuse &&
+ Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) {
+ /* new connection, can multiplex, wake pending handles */
+ process_pending_handles(data->multi);
+ }
multistate(data, MSTATE_PROTOCONNECT);
+ }
else {
multistate(data, MSTATE_CONNECTING);
}
@@ -2081,22 +2014,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */
DEBUGASSERT(data->conn);
result = Curl_http_connect(data, &protocol_connected);
-#ifndef CURL_DISABLE_PROXY
- if(data->conn->bits.proxy_connect_closed) {
+ if(!result) {
rc = CURLM_CALL_MULTI_PERFORM;
- /* connect back to proxy again */
- result = CURLE_OK;
- multi_done(data, CURLE_OK, FALSE);
- multistate(data, MSTATE_CONNECT);
+ /* initiate protocol connect phase */
+ multistate(data, MSTATE_PROTOCONNECT);
}
else
-#endif
- if(!result) {
- rc = CURLM_CALL_MULTI_PERFORM;
- /* initiate protocol connect phase */
- multistate(data, MSTATE_PROTOCONNECT);
- }
- else
stream_error = TRUE;
break;
#endif
@@ -2106,6 +2029,11 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
DEBUGASSERT(data->conn);
result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &connected);
if(connected && !result) {
+ if(!data->conn->bits.reuse &&
+ Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) {
+ /* new connection, can multiplex, wake pending handles */
+ process_pending_handles(data->multi);
+ }
rc = CURLM_CALL_MULTI_PERFORM;
multistate(data, MSTATE_PROTOCONNECT);
}
@@ -2384,24 +2312,22 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
send_timeout_ms = 0;
if(data->set.max_send_speed)
send_timeout_ms =
- Curl_pgrsLimitWaitTime(data->progress.uploaded,
- data->progress.ul_limit_size,
+ Curl_pgrsLimitWaitTime(&data->progress.ul,
data->set.max_send_speed,
- data->progress.ul_limit_start,
*nowp);
recv_timeout_ms = 0;
if(data->set.max_recv_speed)
recv_timeout_ms =
- Curl_pgrsLimitWaitTime(data->progress.downloaded,
- data->progress.dl_limit_size,
+ Curl_pgrsLimitWaitTime(&data->progress.dl,
data->set.max_recv_speed,
- data->progress.dl_limit_start,
*nowp);
if(!send_timeout_ms && !recv_timeout_ms) {
multistate(data, MSTATE_PERFORMING);
Curl_ratelimit(data, *nowp);
+ /* start performing again right away */
+ rc = CURLM_CALL_MULTI_PERFORM;
}
else if(send_timeout_ms >= recv_timeout_ms)
Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST);
@@ -2417,19 +2343,15 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* check if over send speed */
send_timeout_ms = 0;
if(data->set.max_send_speed)
- send_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.uploaded,
- data->progress.ul_limit_size,
+ send_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.ul,
data->set.max_send_speed,
- data->progress.ul_limit_start,
*nowp);
/* check if over recv speed */
recv_timeout_ms = 0;
if(data->set.max_recv_speed)
- recv_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.downloaded,
- data->progress.dl_limit_size,
+ recv_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.dl,
data->set.max_recv_speed,
- data->progress.dl_limit_start,
*nowp);
if(send_timeout_ms || recv_timeout_ms) {
@@ -2443,7 +2365,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
}
/* read/write data if it is ready to do so */
- result = Curl_readwrite(data);
+ result = Curl_sendrecv(data, nowp);
if(data->req.done || (result == CURLE_RECV_ERROR)) {
/* If CURLE_RECV_ERROR happens early enough, we assume it was a race
@@ -2568,10 +2490,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
if(data->conn) {
CURLcode res;
- if(data->conn->bits.multiplex)
- /* Check if we can move pending requests to connection */
- process_pending_handles(multi); /* multiplexing */
-
/* post-transfer command */
res = multi_done(data, result, FALSE);
@@ -2646,12 +2564,7 @@ statemachine_end:
We do not have to do this in every case block above where a
failure is detected */
Curl_detach_connection(data);
-
- /* remove connection from cache */
- Curl_conncache_remove_conn(data, conn, TRUE);
-
- /* disconnect properly */
- Curl_disconnect(data, conn, dead_connection);
+ Curl_cpool_disconnect(data, conn, dead_connection);
}
}
else if(data->mstate == MSTATE_CONNECT) {
@@ -2695,10 +2608,10 @@ statemachine_end:
}
multistate(data, MSTATE_MSGSENT);
- /* add this handle to the list of msgsent handles */
- Curl_llist_append(&multi->msgsent, data, &data->connect_queue);
- /* unlink from the main list */
- unlink_easy(multi, data);
+ /* unlink from the process list */
+ Curl_node_remove(&data->multi_queue);
+ /* add this handle msgsent list */
+ Curl_llist_append(&multi->msgsent, data, &data->multi_queue);
return CURLM_OK;
}
} while((rc == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE));
@@ -2710,10 +2623,11 @@ statemachine_end:
CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles)
{
- struct Curl_easy *data;
CURLMcode returncode = CURLM_OK;
- struct Curl_tree *t;
+ struct Curl_tree *t = NULL;
struct curltime now = Curl_now();
+ struct Curl_llist_node *e;
+ struct Curl_llist_node *n = NULL;
SIGPIPE_VARIABLE(pipe_st);
if(!GOOD_MULTI_HANDLE(multi))
@@ -2723,30 +2637,27 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles)
return CURLM_RECURSIVE_API_CALL;
sigpipe_init(&pipe_st);
- data = multi->easyp;
- if(data) {
+ for(e = Curl_llist_head(&multi->process); e; e = n) {
+ struct Curl_easy *data = Curl_node_elem(e);
CURLMcode result;
/* Do the loop and only alter the signal ignore state if the next handle
has a different NO_SIGNAL state than the previous */
- do {
- /* the current node might be unlinked in multi_runsingle(), get the next
- pointer now */
- struct Curl_easy *datanext = data->next;
-
- if(data != multi->conn_cache.closure_handle) {
- /* connection cache handle is processed below */
- sigpipe_apply(data, &pipe_st);
- result = multi_runsingle(multi, &now, data);
- if(result)
- returncode = result;
- }
- data = datanext; /* operate on next handle */
- } while(data);
+ /* the current node might be unlinked in multi_runsingle(), get the next
+ pointer now */
+ n = Curl_node_next(e);
+
+ if(data != multi->cpool.idata) {
+ /* connection pool handle is processed below */
+ sigpipe_apply(data, &pipe_st);
+ result = multi_runsingle(multi, &now, data);
+ if(result)
+ returncode = result;
+ }
}
- sigpipe_apply(multi->conn_cache.closure_handle, &pipe_st);
- Curl_conncache_multi_perform(multi);
+ sigpipe_apply(multi->cpool.idata, &pipe_st);
+ Curl_cpool_multi_perform(multi);
sigpipe_restore(&pipe_st);
@@ -2764,7 +2675,7 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles)
multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);
if(t) {
/* the removed may have another timeout in queue */
- data = t->payload;
+ struct Curl_easy *data = Curl_splayget(t);
if(data->mstate == MSTATE_PENDING) {
bool stream_unused;
CURLcode result_unused;
@@ -2774,7 +2685,7 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles)
move_pending_to_connect(multi, data);
}
}
- (void)add_next_timeout(now, multi, t->payload);
+ (void)add_next_timeout(now, multi, Curl_splayget(t));
}
} while(t);
@@ -2787,38 +2698,45 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles)
return returncode;
}
-/* unlink_all_msgsent_handles() detaches all those easy handles from this
- multi handle */
+/* unlink_all_msgsent_handles() moves all nodes back from the msgsent list to
+ the process list */
static void unlink_all_msgsent_handles(struct Curl_multi *multi)
{
- struct Curl_llist_element *e = multi->msgsent.head;
- if(e) {
- struct Curl_easy *data = e->ptr;
- DEBUGASSERT(data->mstate == MSTATE_MSGSENT);
- data->multi = NULL;
+ struct Curl_llist_node *e;
+ for(e = Curl_llist_head(&multi->msgsent); e; e = Curl_node_next(e)) {
+ struct Curl_easy *data = Curl_node_elem(e);
+ if(data) {
+ DEBUGASSERT(data->mstate == MSTATE_MSGSENT);
+ Curl_node_remove(&data->multi_queue);
+ /* put it into the process list */
+ Curl_llist_append(&multi->process, data, &data->multi_queue);
+ }
}
}
CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
{
- struct Curl_easy *data;
- struct Curl_easy *nextdata;
-
if(GOOD_MULTI_HANDLE(multi)) {
+ struct Curl_llist_node *e;
+ struct Curl_llist_node *n;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
multi->magic = 0; /* not good anymore */
+ /* move the pending and msgsent entries back to process
+ so that there is just one list to iterate over */
unlink_all_msgsent_handles(multi);
process_pending_handles(multi);
+
/* First remove all remaining easy handles */
- data = multi->easyp;
- while(data) {
+ for(e = Curl_llist_head(&multi->process); e; e = n) {
+ struct Curl_easy *data = Curl_node_elem(e);
+
if(!GOOD_EASY_HANDLE(data))
return CURLM_BAD_HANDLE;
- nextdata = data->next;
+ n = Curl_node_next(e);
if(!data->state.done && data->conn)
/* if DONE was never called for this handle */
(void)multi_done(data, CURLE_OK, TRUE);
@@ -2829,24 +2747,18 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
data->dns.hostcachetype = HCACHE_NONE;
}
- /* Clear the pointer to the connection cache */
- data->state.conn_cache = NULL;
data->multi = NULL; /* clear the association */
#ifdef USE_LIBPSL
if(data->psl == &multi->psl)
data->psl = NULL;
#endif
-
- data = nextdata;
}
- /* Close all the connections in the connection cache */
- Curl_conncache_multi_close_all(multi);
+ Curl_cpool_destroy(&multi->cpool);
sockhash_destroy(&multi->sockhash);
Curl_hash_destroy(&multi->proto_hash);
- Curl_conncache_destroy(&multi->conn_cache);
Curl_hash_destroy(&multi->hostcache);
Curl_psl_destroy(&multi->psl);
@@ -2889,15 +2801,15 @@ CURLMsg *curl_multi_info_read(struct Curl_multi *multi, int *msgs_in_queue)
!multi->in_callback &&
Curl_llist_count(&multi->msglist)) {
/* there is one or more messages in the list */
- struct Curl_llist_element *e;
+ struct Curl_llist_node *e;
/* extract the head of the list to return */
- e = multi->msglist.head;
+ e = Curl_llist_head(&multi->msglist);
- msg = e->ptr;
+ msg = Curl_node_elem(e);
/* remove the extracted entry */
- Curl_llist_remove(&multi->msglist, e, NULL);
+ Curl_node_remove(e);
*msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist));
@@ -2970,18 +2882,24 @@ CURLMcode Curl_multi_pollset_ev(struct Curl_multi *multi,
}
if(last_action && (last_action != cur_action)) {
/* Socket was used already, but different action now */
- if(last_action & CURL_POLL_IN)
+ if(last_action & CURL_POLL_IN) {
+ DEBUGASSERT(entry->readers);
entry->readers--;
- if(last_action & CURL_POLL_OUT)
+ }
+ if(last_action & CURL_POLL_OUT) {
+ DEBUGASSERT(entry->writers);
entry->writers--;
- if(cur_action & CURL_POLL_IN)
+ }
+ if(cur_action & CURL_POLL_IN) {
entry->readers++;
+ }
if(cur_action & CURL_POLL_OUT)
entry->writers++;
}
else if(!last_action &&
!Curl_hash_pick(&entry->transfers, (char *)&data, /* hash key */
sizeof(struct Curl_easy *))) {
+ DEBUGASSERT(entry->users < 100000); /* detect weird values */
/* a new transfer using this socket */
entry->users++;
if(cur_action & CURL_POLL_IN)
@@ -3043,23 +2961,27 @@ CURLMcode Curl_multi_pollset_ev(struct Curl_multi *multi,
if(entry) {
unsigned char oldactions = last_ps->actions[i];
/* this socket has been removed. Decrease user count */
+ DEBUGASSERT(entry->users);
entry->users--;
if(oldactions & CURL_POLL_OUT)
entry->writers--;
if(oldactions & CURL_POLL_IN)
entry->readers--;
if(!entry->users) {
+ bool dead = FALSE;
if(multi->socket_cb) {
set_in_callback(multi, TRUE);
rc = multi->socket_cb(data, s, CURL_POLL_REMOVE,
multi->socket_userp, entry->socketp);
set_in_callback(multi, FALSE);
- if(rc == -1) {
- multi->dead = TRUE;
- return CURLM_ABORTED_BY_CALLBACK;
- }
+ if(rc == -1)
+ dead = TRUE;
}
sh_delentry(entry, &multi->sockhash, s);
+ if(dead) {
+ multi->dead = TRUE;
+ return CURLM_ABORTED_BY_CALLBACK;
+ }
}
else {
/* still users, but remove this handle as a user of this socket */
@@ -3097,11 +3019,15 @@ void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s)
if(data) {
/* if there is still an easy handle associated with this connection */
struct Curl_multi *multi = data->multi;
+ DEBUGF(infof(data, "Curl_multi_closed, fd=%" FMT_SOCKET_T
+ " multi is %p", s, (void *)multi));
if(multi) {
/* this is set if this connection is part of a handle that is added to
a multi handle, and only then this is necessary */
struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s);
+ DEBUGF(infof(data, "Curl_multi_closed, fd=%" FMT_SOCKET_T
+ " entry is %p", s, (void *)entry));
if(entry) {
int rc = 0;
if(multi->socket_cb) {
@@ -3141,26 +3067,24 @@ static CURLMcode add_next_timeout(struct curltime now,
{
struct curltime *tv = &d->state.expiretime;
struct Curl_llist *list = &d->state.timeoutlist;
- struct Curl_llist_element *e;
- struct time_node *node = NULL;
+ struct Curl_llist_node *e;
/* move over the timeout list for this specific handle and remove all
timeouts that are now passed tense and store the next pending
timeout in *tv */
- for(e = list->head; e;) {
- struct Curl_llist_element *n = e->next;
- timediff_t diff;
- node = (struct time_node *)e->ptr;
- diff = Curl_timediff_us(node->time, now);
+ for(e = Curl_llist_head(list); e;) {
+ struct Curl_llist_node *n = Curl_node_next(e);
+ struct time_node *node = Curl_node_elem(e);
+ timediff_t diff = Curl_timediff_us(node->time, now);
if(diff <= 0)
/* remove outdated entry */
- Curl_llist_remove(list, e, NULL);
+ Curl_node_remove(e);
else
/* the list is sorted so get out on the first mismatch */
break;
e = n;
}
- e = list->head;
+ e = Curl_llist_head(list);
if(!e) {
/* clear the expire times within the handles that we remove from the
splay tree */
@@ -3168,6 +3092,7 @@ static CURLMcode add_next_timeout(struct curltime now,
tv->tv_usec = 0;
}
else {
+ struct time_node *node = Curl_node_elem(e);
/* copy the first entry to 'tv' */
memcpy(tv, &node->time, sizeof(*tv));
@@ -3179,6 +3104,59 @@ static CURLMcode add_next_timeout(struct curltime now,
return CURLM_OK;
}
+struct multi_run_ctx {
+ struct Curl_multi *multi;
+ struct curltime now;
+ size_t run_xfers;
+ SIGPIPE_MEMBER(pipe_st);
+ bool run_cpool;
+};
+
+static CURLMcode multi_run_expired(struct multi_run_ctx *mrc)
+{
+ struct Curl_multi *multi = mrc->multi;
+ struct Curl_easy *data = NULL;
+ struct Curl_tree *t = NULL;
+ CURLMcode result = CURLM_OK;
+
+ /*
+ * The loop following here will go on as long as there are expire-times left
+ * to process (compared to mrc->now) in the splay and 'data' will be
+ * re-assigned for every expired handle we deal with.
+ */
+ while(1) {
+ /* Check if there is one (more) expired timer to deal with! This function
+ extracts a matching node if there is one */
+ multi->timetree = Curl_splaygetbest(mrc->now, multi->timetree, &t);
+ if(!t)
+ goto out;
+
+ data = Curl_splayget(t); /* assign this for next loop */
+ if(!data)
+ continue;
+
+ (void)add_next_timeout(mrc->now, multi, data);
+ if(data == multi->cpool.idata) {
+ mrc->run_cpool = TRUE;
+ continue;
+ }
+
+ mrc->run_xfers++;
+ sigpipe_apply(data, &mrc->pipe_st);
+ result = multi_runsingle(multi, &mrc->now, data);
+
+ if(CURLM_OK >= result) {
+ /* get the socket(s) and check if the state has been changed since
+ last */
+ result = singlesocket(multi, data);
+ if(result)
+ goto out;
+ }
+ }
+
+out:
+ return result;
+}
static CURLMcode multi_socket(struct Curl_multi *multi,
bool checkall,
curl_socket_t s,
@@ -3187,28 +3165,31 @@ static CURLMcode multi_socket(struct Curl_multi *multi,
{
CURLMcode result = CURLM_OK;
struct Curl_easy *data = NULL;
- struct Curl_tree *t;
- struct curltime now = Curl_now();
- bool run_conn_cache = FALSE;
- SIGPIPE_VARIABLE(pipe_st);
+ struct multi_run_ctx mrc;
+
+ (void)ev_bitmask;
+ memset(&mrc, 0, sizeof(mrc));
+ mrc.multi = multi;
+ mrc.now = Curl_now();
+ sigpipe_init(&mrc.pipe_st);
if(checkall) {
+ struct Curl_llist_node *e;
/* *perform() deals with running_handles on its own */
result = curl_multi_perform(multi, running_handles);
/* walk through each easy handle and do the socket state change magic
and callbacks */
if(result != CURLM_BAD_HANDLE) {
- data = multi->easyp;
- while(data && !result) {
- result = singlesocket(multi, data);
- data = data->next;
+ for(e = Curl_llist_head(&multi->process); e && !result;
+ e = Curl_node_next(e)) {
+ result = singlesocket(multi, Curl_node_elem(e));
}
}
-
- /* or should we fall-through and do the timer-based stuff? */
- return result;
+ mrc.run_cpool = TRUE;
+ goto out;
}
+
if(s != CURL_SOCKET_TIMEOUT) {
struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s);
@@ -3219,8 +3200,8 @@ static CURLMcode multi_socket(struct Curl_multi *multi,
asked to get removed, so thus we better survive stray socket actions
and just move on. */
/* The socket might come from a connection that is being shut down
- * by the multi's conncache. */
- Curl_conncache_multi_socket(multi, s, ev_bitmask);
+ * by the multi's connection pool. */
+ Curl_cpool_multi_socket(multi, s, ev_bitmask);
}
else {
struct Curl_hash_iterator iter;
@@ -3234,79 +3215,43 @@ static CURLMcode multi_socket(struct Curl_multi *multi,
DEBUGASSERT(data);
DEBUGASSERT(data->magic == CURLEASY_MAGIC_NUMBER);
- if(data == multi->conn_cache.closure_handle)
- run_conn_cache = TRUE;
+ if(data == multi->cpool.idata)
+ mrc.run_cpool = TRUE;
else {
- if(data->conn && !(data->conn->handler->flags & PROTOPT_DIRLOCK))
- /* set socket event bitmask if they are not locked */
- data->state.select_bits |= (unsigned char)ev_bitmask;
-
- Curl_expire(data, 0, EXPIRE_RUN_NOW);
+ /* Expire with out current now, so we will get it below when
+ * asking the splaytree for expired transfers. */
+ Curl_expire_ex(data, &mrc.now, 0, EXPIRE_RUN_NOW);
}
}
-
- /* Now we fall-through and do the timer-based stuff, since we do not want
- to force the user to have to deal with timeouts as long as at least
- one connection in fact has traffic. */
-
- data = NULL; /* set data to NULL again to avoid calling
- multi_runsingle() in case there is no need to */
- now = Curl_now(); /* get a newer time since the multi_runsingle() loop
- may have taken some time */
}
}
- else {
- /* Asked to run due to time-out. Clear the 'lastcall' variable to force
- Curl_update_timer() to trigger a callback to the app again even if the
- same timeout is still the one to run after this call. That handles the
- case when the application asks libcurl to run the timeout
- prematurely. */
- memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall));
- }
- /*
- * The loop following here will go on as long as there are expire-times left
- * to process in the splay and 'data' will be re-assigned for every expired
- * handle we deal with.
- */
- sigpipe_init(&pipe_st);
- do {
- if(data == multi->conn_cache.closure_handle)
- run_conn_cache = TRUE;
- /* the first loop lap 'data' can be NULL */
- else if(data) {
- sigpipe_apply(data, &pipe_st);
- result = multi_runsingle(multi, &now, data);
-
- if(CURLM_OK >= result) {
- /* get the socket(s) and check if the state has been changed since
- last */
- result = singlesocket(multi, data);
- if(result)
- break;
- }
- }
-
- /* Check if there is one (more) expired timer to deal with! This function
- extracts a matching node if there is one */
-
- multi->timetree = Curl_splaygetbest(now, multi->timetree, &t);
- if(t) {
- data = t->payload; /* assign this for next loop */
- (void)add_next_timeout(now, multi, t->payload);
- }
-
- } while(t);
+ result = multi_run_expired(&mrc);
+ if(result)
+ goto out;
- if(run_conn_cache) {
- sigpipe_apply(multi->conn_cache.closure_handle, &pipe_st);
- Curl_conncache_multi_perform(multi);
+ if(mrc.run_xfers) {
+ /* Running transfers takes time. With a new timestamp, we might catch
+ * other expires which are due now. Instead of telling the application
+ * to set a 0 timeout and call us again, we run them here.
+ * Do that only once or it might be unfair to transfers on other
+ * sockets. */
+ mrc.now = Curl_now();
+ result = multi_run_expired(&mrc);
}
- sigpipe_restore(&pipe_st);
+out:
+ if(mrc.run_cpool) {
+ sigpipe_apply(multi->cpool.idata, &mrc.pipe_st);
+ Curl_cpool_multi_perform(multi);
+ }
+ sigpipe_restore(&mrc.pipe_st);
if(running_handles)
*running_handles = (int)multi->num_alive;
+
+ if(CURLM_OK >= result)
+ result = Curl_update_timer(multi);
return result;
}
@@ -3395,39 +3340,28 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi,
CURLMcode curl_multi_socket(struct Curl_multi *multi, curl_socket_t s,
int *running_handles)
{
- CURLMcode result;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
- result = multi_socket(multi, FALSE, s, 0, running_handles);
- if(CURLM_OK >= result)
- result = Curl_update_timer(multi);
- return result;
+ return multi_socket(multi, FALSE, s, 0, running_handles);
}
CURLMcode curl_multi_socket_action(struct Curl_multi *multi, curl_socket_t s,
int ev_bitmask, int *running_handles)
{
- CURLMcode result;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
- result = multi_socket(multi, FALSE, s, ev_bitmask, running_handles);
- if(CURLM_OK >= result)
- result = Curl_update_timer(multi);
- return result;
+ return multi_socket(multi, FALSE, s, ev_bitmask, running_handles);
}
CURLMcode curl_multi_socket_all(struct Curl_multi *multi, int *running_handles)
{
- CURLMcode result;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
- result = multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles);
- if(CURLM_OK >= result)
- result = Curl_update_timer(multi);
- return result;
+ return multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles);
}
static CURLMcode multi_timeout(struct Curl_multi *multi,
+ struct curltime *expire_time,
long *timeout_ms)
{
static const struct curltime tv_zero = {0, 0};
@@ -3443,20 +3377,29 @@ static CURLMcode multi_timeout(struct Curl_multi *multi,
/* splay the lowest to the bottom */
multi->timetree = Curl_splay(tv_zero, multi->timetree);
-
- if(Curl_splaycomparekeys(multi->timetree->key, now) > 0) {
+ /* this will not return NULL from a non-emtpy tree, but some compilers
+ * are not convinced of that. Analyzers are hard. */
+ *expire_time = multi->timetree? multi->timetree->key : tv_zero;
+
+ /* 'multi->timetree' will be non-NULL here but the compilers sometimes
+ yell at us if we assume so */
+ if(multi->timetree &&
+ Curl_timediff_us(multi->timetree->key, now) > 0) {
/* some time left before expiration */
timediff_t diff = Curl_timediff_ceil(multi->timetree->key, now);
/* this should be safe even on 32-bit archs, as we do not use that
overly long timeouts */
*timeout_ms = (long)diff;
}
- else
+ else {
/* 0 means immediately */
*timeout_ms = 0;
+ }
}
- else
+ else {
+ *expire_time = tv_zero;
*timeout_ms = -1;
+ }
return CURLM_OK;
}
@@ -3464,6 +3407,8 @@ static CURLMcode multi_timeout(struct Curl_multi *multi,
CURLMcode curl_multi_timeout(struct Curl_multi *multi,
long *timeout_ms)
{
+ struct curltime expire_time;
+
/* First, make some basic checks that the CURLM handle is a good handle */
if(!GOOD_MULTI_HANDLE(multi))
return CURLM_BAD_HANDLE;
@@ -3471,56 +3416,79 @@ CURLMcode curl_multi_timeout(struct Curl_multi *multi,
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
- return multi_timeout(multi, timeout_ms);
+ return multi_timeout(multi, &expire_time, timeout_ms);
}
+#define DEBUG_UPDATE_TIMER 0
+
/*
* Tell the application it should update its timers, if it subscribes to the
* update timer callback.
*/
CURLMcode Curl_update_timer(struct Curl_multi *multi)
{
+ struct curltime expire_ts;
long timeout_ms;
int rc;
+ bool set_value = FALSE;
if(!multi->timer_cb || multi->dead)
return CURLM_OK;
- if(multi_timeout(multi, &timeout_ms)) {
+ if(multi_timeout(multi, &expire_ts, &timeout_ms)) {
return CURLM_OK;
}
- if(timeout_ms < 0) {
- static const struct curltime none = {0, 0};
- if(Curl_splaycomparekeys(none, multi->timer_lastcall)) {
- multi->timer_lastcall = none;
- /* there is no timeout now but there was one previously, tell the app to
- disable it */
- set_in_callback(multi, TRUE);
- rc = multi->timer_cb(multi, -1, multi->timer_userp);
- set_in_callback(multi, FALSE);
- if(rc == -1) {
- multi->dead = TRUE;
- return CURLM_ABORTED_BY_CALLBACK;
- }
- return CURLM_OK;
- }
- return CURLM_OK;
- }
-
- /* When multi_timeout() is done, multi->timetree points to the node with the
- * timeout we got the (relative) time-out time for. We can thus easily check
- * if this is the same (fixed) time as we got in a previous call and then
- * avoid calling the callback again. */
- if(Curl_splaycomparekeys(multi->timetree->key, multi->timer_lastcall) == 0)
- return CURLM_OK;
- multi->timer_lastcall = multi->timetree->key;
+ if(timeout_ms < 0 && multi->last_timeout_ms < 0) {
+#if DEBUG_UPDATE_TIMER
+ fprintf(stderr, "Curl_update_timer(), still no timeout, no change\n");
+#endif
+ }
+ else if(timeout_ms < 0) {
+ /* there is no timeout now but there was one previously */
+#if DEBUG_UPDATE_TIMER
+ fprintf(stderr, "Curl_update_timer(), remove timeout, "
+ " last_timeout=%ldms\n", multi->last_timeout_ms);
+#endif
+ timeout_ms = -1; /* normalize */
+ set_value = TRUE;
+ }
+ else if(multi->last_timeout_ms < 0) {
+#if DEBUG_UPDATE_TIMER
+ fprintf(stderr, "Curl_update_timer(), had no timeout, set now\n");
+#endif
+ set_value = TRUE;
+ }
+ else if(Curl_timediff_us(multi->last_expire_ts, expire_ts)) {
+ /* We had a timeout before and have one now, the absolute timestamp
+ * differs. The relative timeout_ms may be the same, but the starting
+ * point differs. Let the application restart its timer. */
+#if DEBUG_UPDATE_TIMER
+ fprintf(stderr, "Curl_update_timer(), expire timestamp changed\n");
+#endif
+ set_value = TRUE;
+ }
+ else {
+ /* We have same expire time as previously. Our relative 'timeout_ms'
+ * may be different now, but the application has the timer running
+ * and we do not to tell it to start this again. */
+#if DEBUG_UPDATE_TIMER
+ fprintf(stderr, "Curl_update_timer(), same expire timestamp, no change\n");
+#endif
+ }
- set_in_callback(multi, TRUE);
- rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp);
- set_in_callback(multi, FALSE);
- if(rc == -1) {
- multi->dead = TRUE;
- return CURLM_ABORTED_BY_CALLBACK;
+ if(set_value) {
+#if DEBUG_UPDATE_TIMER
+ fprintf(stderr, "Curl_update_timer(), set timeout %ldms\n", timeout_ms);
+#endif
+ multi->last_expire_ts = expire_ts;
+ multi->last_timeout_ms = timeout_ms;
+ set_in_callback(multi, TRUE);
+ rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp);
+ set_in_callback(multi, FALSE);
+ if(rc == -1) {
+ multi->dead = TRUE;
+ return CURLM_ABORTED_BY_CALLBACK;
+ }
}
return CURLM_OK;
}
@@ -3533,13 +3501,13 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi)
static void
multi_deltimeout(struct Curl_easy *data, expire_id eid)
{
- struct Curl_llist_element *e;
+ struct Curl_llist_node *e;
struct Curl_llist *timeoutlist = &data->state.timeoutlist;
/* find and remove the specific node from the list */
- for(e = timeoutlist->head; e; e = e->next) {
- struct time_node *n = (struct time_node *)e->ptr;
+ for(e = Curl_llist_head(timeoutlist); e; e = Curl_node_next(e)) {
+ struct time_node *n = Curl_node_elem(e);
if(n->eid == eid) {
- Curl_llist_remove(timeoutlist, e, NULL);
+ Curl_node_remove(e);
return;
}
}
@@ -3557,9 +3525,9 @@ multi_addtimeout(struct Curl_easy *data,
struct curltime *stamp,
expire_id eid)
{
- struct Curl_llist_element *e;
+ struct Curl_llist_node *e;
struct time_node *node;
- struct Curl_llist_element *prev = NULL;
+ struct Curl_llist_node *prev = NULL;
size_t n;
struct Curl_llist *timeoutlist = &data->state.timeoutlist;
@@ -3572,8 +3540,8 @@ multi_addtimeout(struct Curl_easy *data,
n = Curl_llist_count(timeoutlist);
if(n) {
/* find the correct spot in the list */
- for(e = timeoutlist->head; e; e = e->next) {
- struct time_node *check = (struct time_node *)e->ptr;
+ for(e = Curl_llist_head(timeoutlist); e; e = Curl_node_next(e)) {
+ struct time_node *check = Curl_node_elem(e);
timediff_t diff = Curl_timediff(check->time, node->time);
if(diff > 0)
break;
@@ -3599,10 +3567,12 @@ multi_addtimeout(struct Curl_easy *data,
*
* Expire replaces a former timeout using the same id if already set.
*/
-void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id)
+static void Curl_expire_ex(struct Curl_easy *data,
+ const struct curltime *nowp,
+ timediff_t milli, expire_id id)
{
struct Curl_multi *multi = data->multi;
- struct curltime *nowp = &data->state.expiretime;
+ struct curltime *curr_expire = &data->state.expiretime;
struct curltime set;
/* this is only interesting while there is still an associated multi struct
@@ -3612,7 +3582,7 @@ void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id)
DEBUGASSERT(id < EXPIRE_LAST);
- set = Curl_now();
+ set = *nowp;
set.tv_sec += (time_t)(milli/1000); /* might be a 64 to 32 bits conversion */
set.tv_usec += (int)(milli%1000)*1000;
@@ -3628,11 +3598,11 @@ void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id)
in case we need to recompute the minimum timer later. */
multi_addtimeout(data, &set, id);
- if(nowp->tv_sec || nowp->tv_usec) {
+ if(curr_expire->tv_sec || curr_expire->tv_usec) {
/* This means that the struct is added as a node in the splay tree.
Compare if the new time is earlier, and only remove-old/add-new if it
is. */
- timediff_t diff = Curl_timediff(set, *nowp);
+ timediff_t diff = Curl_timediff(set, *curr_expire);
int rc;
if(diff > 0) {
@@ -3651,12 +3621,18 @@ void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id)
/* Indicate that we are in the splay tree and insert the new timer expiry
value since it is our local minimum. */
- *nowp = set;
- data->state.timenode.payload = data;
- multi->timetree = Curl_splayinsert(*nowp, multi->timetree,
+ *curr_expire = set;
+ Curl_splayset(&data->state.timenode, data);
+ multi->timetree = Curl_splayinsert(*curr_expire, multi->timetree,
&data->state.timenode);
}
+void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id)
+{
+ struct curltime now = Curl_now();
+ Curl_expire_ex(data, &now, milli, id);
+}
+
/*
* Curl_expire_done()
*
@@ -3674,7 +3650,7 @@ void Curl_expire_done(struct Curl_easy *data, expire_id id)
*
* Clear ALL timeout values for this handle.
*/
-void Curl_expire_clear(struct Curl_easy *data)
+bool Curl_expire_clear(struct Curl_easy *data)
{
struct Curl_multi *multi = data->multi;
struct curltime *nowp = &data->state.expiretime;
@@ -3682,7 +3658,7 @@ void Curl_expire_clear(struct Curl_easy *data)
/* this is only interesting while there is still an associated multi struct
remaining! */
if(!multi)
- return;
+ return FALSE;
if(nowp->tv_sec || nowp->tv_usec) {
/* Since this is an cleared time, we must remove the previous entry from
@@ -3695,22 +3671,19 @@ void Curl_expire_clear(struct Curl_easy *data)
if(rc)
infof(data, "Internal error clearing splay node = %d", rc);
- /* flush the timeout list too */
- while(list->size > 0) {
- Curl_llist_remove(list, list->tail, NULL);
- }
+ /* clear the timeout list too */
+ Curl_llist_destroy(list, NULL);
#ifdef DEBUGBUILD
infof(data, "Expire cleared");
#endif
nowp->tv_sec = 0;
nowp->tv_usec = 0;
+ return TRUE;
}
+ return FALSE;
}
-
-
-
CURLMcode curl_multi_assign(struct Curl_multi *multi, curl_socket_t s,
void *hashp)
{
@@ -3726,52 +3699,24 @@ CURLMcode curl_multi_assign(struct Curl_multi *multi, curl_socket_t s,
return CURLM_OK;
}
-size_t Curl_multi_max_host_connections(struct Curl_multi *multi)
-{
- return multi ? (size_t)multi->max_host_connections : 0;
-}
-
-size_t Curl_multi_max_total_connections(struct Curl_multi *multi)
-{
- return multi ? (size_t)multi->max_total_connections : 0;
-}
-
-/*
- * When information about a connection has appeared, call this!
- */
-
-void Curl_multiuse_state(struct Curl_easy *data,
- int bundlestate) /* use BUNDLE_* defines */
-{
- struct connectdata *conn;
- DEBUGASSERT(data);
- DEBUGASSERT(data->multi);
- conn = data->conn;
- DEBUGASSERT(conn);
- DEBUGASSERT(conn->bundle);
-
- conn->bundle->multiuse = bundlestate;
- process_pending_handles(data->multi);
-}
-
static void move_pending_to_connect(struct Curl_multi *multi,
struct Curl_easy *data)
{
DEBUGASSERT(data->mstate == MSTATE_PENDING);
- /* put it back into the main list */
- link_easy(multi, data);
+ /* Remove this node from the pending list */
+ Curl_node_remove(&data->multi_queue);
- multistate(data, MSTATE_CONNECT);
+ /* put it into the process list */
+ Curl_llist_append(&multi->process, data, &data->multi_queue);
- /* Remove this node from the pending list */
- Curl_llist_remove(&multi->pending, &data->connect_queue, NULL);
+ multistate(data, MSTATE_CONNECT);
/* Make sure that the handle will be processed soonish. */
Curl_expire(data, 0, EXPIRE_RUN_NOW);
}
-/* process_pending_handles() moves a handle from PENDING back into the main
+/* process_pending_handles() moves a handle from PENDING back into the process
list and change state to CONNECT.
We do not move all transfers because that can be a significant amount.
@@ -3787,9 +3732,9 @@ static void move_pending_to_connect(struct Curl_multi *multi,
*/
static void process_pending_handles(struct Curl_multi *multi)
{
- struct Curl_llist_element *e = multi->pending.head;
+ struct Curl_llist_node *e = Curl_llist_head(&multi->pending);
if(e) {
- struct Curl_easy *data = e->ptr;
+ struct Curl_easy *data = Curl_node_elem(e);
move_pending_to_connect(multi, data);
}
}
@@ -3817,12 +3762,12 @@ struct Curl_easy **curl_multi_get_handles(struct Curl_multi *multi)
(multi->num_easy + 1));
if(a) {
unsigned int i = 0;
- struct Curl_easy *e = multi->easyp;
- while(e) {
+ struct Curl_llist_node *e;
+ for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) {
+ struct Curl_easy *data = Curl_node_elem(e);
DEBUGASSERT(i < multi->num_easy);
- if(!e->state.internal)
- a[i++] = e;
- e = e->next;
+ if(!data->state.internal)
+ a[i++] = data;
}
a[i] = NULL; /* last entry is a NULL */
}
@@ -3945,3 +3890,32 @@ static void multi_xfer_bufs_free(struct Curl_multi *multi)
multi->xfer_ulbuf_len = 0;
multi->xfer_ulbuf_borrowed = FALSE;
}
+
+struct Curl_easy *Curl_multi_get_handle(struct Curl_multi *multi,
+ curl_off_t mid)
+{
+
+ if(mid >= 0) {
+ struct Curl_easy *data;
+ struct Curl_llist_node *e;
+
+ for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) {
+ data = Curl_node_elem(e);
+ if(data->mid == mid)
+ return data;
+ }
+ /* may be in msgsent queue */
+ for(e = Curl_llist_head(&multi->msgsent); e; e = Curl_node_next(e)) {
+ data = Curl_node_elem(e);
+ if(data->mid == mid)
+ return data;
+ }
+ /* may be in pending queue */
+ for(e = Curl_llist_head(&multi->pending); e; e = Curl_node_next(e)) {
+ data = Curl_node_elem(e);
+ if(data->mid == mid)
+ return data;
+ }
+ }
+ return NULL;
+}