diff options
Diffstat (limited to 'libs/libcurl/src/multi.c')
-rw-r--r-- | libs/libcurl/src/multi.c | 545 |
1 files changed, 286 insertions, 259 deletions
diff --git a/libs/libcurl/src/multi.c b/libs/libcurl/src/multi.c index 56b3faf2fa..130226f561 100644 --- a/libs/libcurl/src/multi.c +++ b/libs/libcurl/src/multi.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2019, 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 @@ -114,7 +114,7 @@ static void Curl_init_completed(struct Curl_easy *data) /* Important: reset the conn pointer so that we don't point to memory that could be freed anytime */ - data->easy_conn = NULL; + Curl_detach_connnection(data); Curl_expire_clear(data); /* stop all timers */ } @@ -163,8 +163,8 @@ static void mstate(struct Curl_easy *data, CURLMstate state data->mstate < CURLM_STATE_COMPLETED) { long connection_id = -5000; - if(data->easy_conn) - connection_id = data->easy_conn->connection_id; + if(data->conn) + connection_id = data->conn->connection_id; infof(data, "STATE: %s => %s handle %p; line %d (connection #%ld)\n", @@ -189,14 +189,17 @@ static void mstate(struct Curl_easy *data, CURLMstate state #endif /* - * We add one of these structs to the sockhash for a particular socket + * We add one of these structs to the sockhash for each socket */ struct Curl_sh_entry { - struct Curl_easy *easy; - int action; /* what action READ/WRITE this socket waits for */ - curl_socket_t socket; /* mainly to ease debugging */ + struct curl_llist list; /* list of easy handles using this socket */ + unsigned int action; /* what combined action READ/WRITE this socket waits + for */ void *socketp; /* settable by users with curl_multi_assign() */ + unsigned int users; /* number of transfers using this */ + unsigned int readers; /* this many transfers want to read */ + unsigned int writers; /* this many transfers want to write */ }; /* bits for 'action' having no bits means this socket is not expecting any action */ @@ -215,8 +218,7 @@ static struct Curl_sh_entry *sh_getentry(struct curl_hash *sh, /* make sure this socket is present in the hash for this handle */ static struct Curl_sh_entry *sh_addentry(struct curl_hash *sh, - curl_socket_t s, - struct Curl_easy *data) + curl_socket_t s) { struct Curl_sh_entry *there = sh_getentry(sh, s); struct Curl_sh_entry *check; @@ -230,8 +232,7 @@ static struct Curl_sh_entry *sh_addentry(struct curl_hash *sh, if(!check) return NULL; /* major failure */ - check->easy = data; - check->socket = s; + Curl_llist_init(&check->list, NULL); /* make/add new hash entry */ if(!Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) { @@ -516,31 +517,23 @@ static void debug_print_sock_hash(void *p) } #endif -static CURLcode multi_done(struct connectdata **connp, - CURLcode status, /* an error if this is called - after an error was detected */ - bool premature) +static CURLcode multi_done(struct Curl_easy *data, + CURLcode status, /* an error if this is called + after an error was detected */ + bool premature) { CURLcode result; - struct connectdata *conn; - struct Curl_easy *data; + struct connectdata *conn = data->conn; unsigned int i; - DEBUGASSERT(*connp); - - conn = *connp; - data = conn->data; - DEBUGF(infof(data, "multi_done\n")); if(data->state.done) /* Stop if multi_done() has already been called */ return CURLE_OK; - if(data->mstate == CURLM_STATE_WAITRESOLVE) { - /* still waiting for the resolve to complete */ - (void)Curl_resolver_wait_resolv(conn, NULL); - } + /* Stop the resolver and free its own resources (but not dns_entry yet). */ + Curl_resolver_kill(conn); Curl_getoff_all_pipelines(data, conn); @@ -579,7 +572,7 @@ static CURLcode multi_done(struct connectdata **connp, if(conn->send_pipe.size || conn->recv_pipe.size) { /* Stop if pipeline is not empty . */ - data->easy_conn = NULL; + Curl_detach_connnection(data); DEBUGF(infof(data, "Connection still in use %zu/%zu, " "no more multi_done now!\n", conn->send_pipe.size, conn->recv_pipe.size)); @@ -587,7 +580,6 @@ static CURLcode multi_done(struct connectdata **connp, } data->state.done = TRUE; /* called just now! */ - Curl_resolver_cancel(conn); if(conn->dns_entry) { Curl_resolv_unlock(data, conn->dns_entry); /* done with this */ @@ -653,10 +645,7 @@ static CURLcode multi_done(struct connectdata **connp, data->state.lastconnect = NULL; } - *connp = NULL; /* to make the caller of this function better detect that - this was either closed or handed over to the connection - cache here, and therefore cannot be used from this point on - */ + Curl_detach_connnection(data); Curl_free_request_state(data); return result; } @@ -685,7 +674,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, return CURLM_RECURSIVE_API_CALL; premature = (data->mstate < CURLM_STATE_COMPLETED) ? TRUE : FALSE; - easy_owns_conn = (data->easy_conn && (data->easy_conn->data == easy)) ? + easy_owns_conn = (data->conn && (data->conn->data == easy)) ? TRUE : FALSE; /* If the 'state' is not INIT or COMPLETED, we might need to do something @@ -696,16 +685,16 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, multi->num_alive--; } - if(data->easy_conn && + if(data->conn && data->mstate > CURLM_STATE_DO && data->mstate < CURLM_STATE_COMPLETED) { /* Set connection owner so that the DONE function closes it. We can safely do this here since connection is killed. */ - data->easy_conn->data = easy; + data->conn->data = easy; /* If the handle is in a pipeline and has started sending off its request but not received its response yet, we need to close connection. */ - streamclose(data->easy_conn, "Removed with partial response"); + streamclose(data->conn, "Removed with partial response"); easy_owns_conn = TRUE; } @@ -714,7 +703,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, curl_easy_cleanup is called. */ Curl_expire_clear(data); - if(data->easy_conn) { + if(data->conn) { /* we must call multi_done() here (if we still own the connection) so that we don't leave a half-baked one around */ @@ -725,11 +714,11 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, Note that this ignores the return code simply because there's nothing really useful to do with it anyway! */ - (void)multi_done(&data->easy_conn, data->result, premature); + (void)multi_done(data, data->result, premature); } else /* Clear connection pipelines, if multi_done above was not called */ - Curl_getoff_all_pipelines(data, data->easy_conn); + Curl_getoff_all_pipelines(data, data->conn); } if(data->connect_queue.ptr) @@ -761,9 +750,9 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, vanish with this handle */ /* Remove the association between the connection and the handle */ - if(data->easy_conn) { - data->easy_conn->data = NULL; - data->easy_conn = NULL; + if(data->conn) { + data->conn->data = NULL; + Curl_detach_connnection(data); } #ifdef USE_LIBPSL @@ -813,9 +802,19 @@ bool Curl_pipeline_wanted(const struct Curl_multi *multi, int bits) return (multi && (multi->pipelining & bits)) ? TRUE : FALSE; } -void Curl_multi_handlePipeBreak(struct Curl_easy *data) +/* This is the only function that should clear data->conn. This will + occasionally be called with the pointer already cleared. */ +void Curl_detach_connnection(struct Curl_easy *data) +{ + data->conn = NULL; +} + +/* This is the only function that should assign data->conn */ +void Curl_attach_connnection(struct Curl_easy *data, + struct connectdata *conn) { - data->easy_conn = NULL; + DEBUGASSERT(!data->conn); + data->conn = conn; } static int waitconnect_getsock(struct connectdata *conn, @@ -879,13 +878,13 @@ static int multi_getsock(struct Curl_easy *data, /* The no connection case can happen when this is called from curl_multi_remove_handle() => singlesocket() => multi_getsock(). */ - if(!data->easy_conn) + if(!data->conn) return 0; if(data->mstate > CURLM_STATE_CONNECT && data->mstate < CURLM_STATE_COMPLETED) { /* Set up ownership correctly */ - data->easy_conn->data = data; + data->conn->data = data; } switch(data->mstate) { @@ -906,31 +905,31 @@ static int multi_getsock(struct Curl_easy *data, return 0; case CURLM_STATE_WAITRESOLVE: - return Curl_resolv_getsock(data->easy_conn, socks, numsocks); + return Curl_resolv_getsock(data->conn, socks, numsocks); case CURLM_STATE_PROTOCONNECT: case CURLM_STATE_SENDPROTOCONNECT: - return Curl_protocol_getsock(data->easy_conn, socks, numsocks); + return Curl_protocol_getsock(data->conn, socks, numsocks); case CURLM_STATE_DO: case CURLM_STATE_DOING: - return Curl_doing_getsock(data->easy_conn, socks, numsocks); + return Curl_doing_getsock(data->conn, socks, numsocks); case CURLM_STATE_WAITPROXYCONNECT: - return waitproxyconnect_getsock(data->easy_conn, socks, numsocks); + return waitproxyconnect_getsock(data->conn, socks, numsocks); case CURLM_STATE_WAITCONNECT: - return waitconnect_getsock(data->easy_conn, socks, numsocks); + return waitconnect_getsock(data->conn, socks, numsocks); case CURLM_STATE_DO_MORE: - return domore_getsock(data->easy_conn, socks, numsocks); + return domore_getsock(data->conn, socks, numsocks); case CURLM_STATE_DO_DONE: /* since is set after DO is completed, we switch to waiting for the same as the *PERFORM states */ case CURLM_STATE_PERFORM: case CURLM_STATE_WAITPERFORM: - return Curl_single_getsock(data->easy_conn, socks, numsocks); + return Curl_single_getsock(data->conn, socks, numsocks); } } @@ -1202,17 +1201,16 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, /* take this handle to the perform state right away */ multistate(data, CURLM_STATE_PERFORM); - data->easy_conn = conn; + Curl_attach_connnection(data, conn); k->keepon |= KEEP_RECV; /* setup to receive! */ } return rc; } -static CURLcode multi_reconnect_request(struct connectdata **connp) +static CURLcode multi_reconnect_request(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct connectdata *conn = *connp; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; /* This was a re-use of a connection and we got a write error in the * DO-phase. Then we DISCONNECT this connection and have another attempt to @@ -1223,11 +1221,9 @@ static CURLcode multi_reconnect_request(struct connectdata **connp) infof(data, "Re-used connection seems dead, get a new one\n"); connclose(conn, "Reconnect dead connection"); /* enforce close */ - result = multi_done(&conn, result, FALSE); /* we are so done with this */ + result = multi_done(data, result, FALSE); /* we are so done with this */ - /* conn may no longer be a good pointer, clear it to avoid mistakes by - parent functions */ - *connp = NULL; + /* data->conn was detached in multi_done() */ /* * We need to check for CURLE_SEND_ERROR here as well. This could happen @@ -1239,11 +1235,11 @@ static CURLcode multi_reconnect_request(struct connectdata **connp) bool protocol_done = TRUE; /* Now, redo the connect and get a new connection */ - result = Curl_connect(data, connp, &async, &protocol_done); + result = Curl_connect(data, &async, &protocol_done); if(!result) { /* We have connected or sent away a name resolve query fine */ - conn = *connp; /* setup conn to again point to something nice */ + conn = data->conn; /* in case it was updated */ if(async) { /* Now, if async is TRUE here, we need to wait for the name to resolve */ @@ -1276,11 +1272,10 @@ static void do_complete(struct connectdata *conn) Curl_pgrsTime(conn->data, TIMER_PRETRANSFER); } -static CURLcode multi_do(struct connectdata **connp, bool *done) +static CURLcode multi_do(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; - struct connectdata *conn = *connp; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; if(conn->handler->do_it) { /* generic protocol-specific function pointer set in curl_connect() */ @@ -1294,12 +1289,12 @@ static CURLcode multi_do(struct connectdata **connp, bool *done) * figure out how to re-establish the connection. */ if(!data->multi) { - result = multi_reconnect_request(connp); + result = multi_reconnect_request(data); if(!result) { /* ... finally back to actually retry the DO phase */ - conn = *connp; /* re-assign conn since multi_reconnect_request - creates a new connection */ + conn = data->conn; /* re-assign conn since multi_reconnect_request + creates a new connection */ result = conn->handler->do_it(conn, done); } } @@ -1368,13 +1363,13 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, bool stream_error = FALSE; rc = CURLM_OK; - if(!data->easy_conn && + if(!data->conn && data->mstate > CURLM_STATE_CONNECT && data->mstate < CURLM_STATE_DONE) { - /* In all these states, the code will blindly access 'data->easy_conn' + /* In all these states, the code will blindly access 'data->conn' so this is precaution that it isn't NULL. And it silences static analyzers. */ - failf(data, "In state %d with no easy_conn, bail out!\n", data->mstate); + failf(data, "In state %d with no conn, bail out!\n", data->mstate); return CURLM_INTERNAL_ERROR; } @@ -1383,13 +1378,13 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, process_pending_handles(multi); /* pipelined/multiplexed */ } - if(data->easy_conn && data->mstate > CURLM_STATE_CONNECT && + if(data->conn && data->mstate > CURLM_STATE_CONNECT && data->mstate < CURLM_STATE_COMPLETED) { /* Make sure we set the connection's current owner */ - data->easy_conn->data = data; + data->conn->data = data; } - if(data->easy_conn && + if(data->conn && (data->mstate >= CURLM_STATE_CONNECT) && (data->mstate < CURLM_STATE_COMPLETED)) { /* we need to wait for the connect state as only then is the start time @@ -1401,23 +1396,26 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(timeout_ms < 0) { /* Handle timed out */ if(data->mstate == CURLM_STATE_WAITRESOLVE) - failf(data, "Resolving timed out after %ld milliseconds", + failf(data, "Resolving timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds", Curl_timediff(now, data->progress.t_startsingle)); else if(data->mstate == CURLM_STATE_WAITCONNECT) - failf(data, "Connection timed out after %ld milliseconds", + failf(data, "Connection timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds", Curl_timediff(now, data->progress.t_startsingle)); else { k = &data->req; if(k->size != -1) { - failf(data, "Operation timed out after %ld milliseconds with %" - CURL_FORMAT_CURL_OFF_T " out of %" + 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", Curl_timediff(now, data->progress.t_startsingle), k->bytecount, k->size); } else { - failf(data, "Operation timed out after %ld milliseconds with %" - CURL_FORMAT_CURL_OFF_T " bytes received", + failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds with %" CURL_FORMAT_CURL_OFF_T + " bytes received", Curl_timediff(now, data->progress.t_startsingle), k->bytecount); } @@ -1425,11 +1423,11 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* Force connection closed if the connection has indeed been used */ if(data->mstate > CURLM_STATE_DO) { - streamclose(data->easy_conn, "Disconnected with pending data"); + streamclose(data->conn, "Disconnected with pending data"); stream_error = TRUE; } result = CURLE_OPERATION_TIMEDOUT; - (void)multi_done(&data->easy_conn, result, TRUE); + (void)multi_done(data, result, TRUE); /* Skip the statemachine and go directly to error handling section. */ goto statemachine_end; } @@ -1456,8 +1454,13 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case CURLM_STATE_CONNECT: /* Connect. We want to get a connection identifier filled in. */ Curl_pgrsTime(data, TIMER_STARTSINGLE); - result = Curl_connect(data, &data->easy_conn, - &async, &protocol_connect); + if(data->set.timeout) + Curl_expire(data, data->set.timeout, EXPIRE_TIMEOUT); + + if(data->set.connecttimeout) + Curl_expire(data, data->set.connecttimeout, EXPIRE_CONNECTTIMEOUT); + + result = Curl_connect(data, &async, &protocol_connect); if(CURLE_NO_CONNECTION_AVAILABLE == result) { /* There was no connection available. We will go to the pending state and wait for an available connection. */ @@ -1472,7 +1475,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(!result) { /* Add this handle to the send or pend pipeline */ - result = Curl_add_handle_to_pipeline(data, data->easy_conn); + result = Curl_add_handle_to_pipeline(data, data->conn); if(result) stream_error = TRUE; else { @@ -1490,7 +1493,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, CURLM_STATE_WAITDO:CURLM_STATE_DO); else { #ifndef CURL_DISABLE_HTTP - if(Curl_connect_ongoing(data->easy_conn)) + if(Curl_connect_ongoing(data->conn)) multistate(data, CURLM_STATE_WAITPROXYCONNECT); else #endif @@ -1505,7 +1508,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* awaiting an asynch name resolve to complete */ { struct Curl_dns_entry *dns = NULL; - struct connectdata *conn = data->easy_conn; + struct connectdata *conn = data->conn; const char *hostname; if(conn->bits.httpproxy) @@ -1528,7 +1531,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } if(!dns) - result = Curl_resolv_check(data->easy_conn, &dns); + result = Curl_resolv_check(data->conn, &dns); /* Update sockets here, because the socket(s) may have been closed and the application thus needs to be told, even if it @@ -1541,12 +1544,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(dns) { /* Perform the next step in the connection phase, and then move on to the WAITCONNECT state */ - result = Curl_once_resolved(data->easy_conn, &protocol_connect); + result = Curl_once_resolved(data->conn, &protocol_connect); if(result) /* if Curl_once_resolved() returns failure, the connection struct is already freed and gone */ - data->easy_conn = NULL; /* no more connection */ + Curl_detach_connnection(data); /* no more connection */ else { /* call again please so that we get the next socket setup */ rc = CURLM_CALL_MULTI_PERFORM; @@ -1555,7 +1558,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, CURLM_STATE_WAITDO:CURLM_STATE_DO); else { #ifndef CURL_DISABLE_HTTP - if(Curl_connect_ongoing(data->easy_conn)) + if(Curl_connect_ongoing(data->conn)) multistate(data, CURLM_STATE_WAITPROXYCONNECT); else #endif @@ -1575,19 +1578,19 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, #ifndef CURL_DISABLE_HTTP case CURLM_STATE_WAITPROXYCONNECT: /* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */ - result = Curl_http_connect(data->easy_conn, &protocol_connect); + result = Curl_http_connect(data->conn, &protocol_connect); - if(data->easy_conn->bits.proxy_connect_closed) { + if(data->conn->bits.proxy_connect_closed) { rc = CURLM_CALL_MULTI_PERFORM; /* connect back to proxy again */ result = CURLE_OK; - multi_done(&data->easy_conn, CURLE_OK, FALSE); + multi_done(data, CURLE_OK, FALSE); multistate(data, CURLM_STATE_CONNECT); } else if(!result) { - if((data->easy_conn->http_proxy.proxytype != CURLPROXY_HTTPS || - data->easy_conn->bits.proxy_ssl_connected[FIRSTSOCKET]) && - Curl_connect_complete(data->easy_conn)) { + if((data->conn->http_proxy.proxytype != CURLPROXY_HTTPS || + data->conn->bits.proxy_ssl_connected[FIRSTSOCKET]) && + Curl_connect_complete(data->conn)) { rc = CURLM_CALL_MULTI_PERFORM; /* initiate protocol connect phase */ multistate(data, CURLM_STATE_SENDPROTOCONNECT); @@ -1600,18 +1603,18 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case CURLM_STATE_WAITCONNECT: /* awaiting a completion of an asynch TCP connect */ - result = Curl_is_connected(data->easy_conn, FIRSTSOCKET, &connected); + result = Curl_is_connected(data->conn, FIRSTSOCKET, &connected); if(connected && !result) { #ifndef CURL_DISABLE_HTTP - if((data->easy_conn->http_proxy.proxytype == CURLPROXY_HTTPS && - !data->easy_conn->bits.proxy_ssl_connected[FIRSTSOCKET]) || - Curl_connect_ongoing(data->easy_conn)) { + if((data->conn->http_proxy.proxytype == CURLPROXY_HTTPS && + !data->conn->bits.proxy_ssl_connected[FIRSTSOCKET]) || + Curl_connect_ongoing(data->conn)) { multistate(data, CURLM_STATE_WAITPROXYCONNECT); break; } #endif rc = CURLM_CALL_MULTI_PERFORM; - multistate(data, data->easy_conn->bits.tunnel_proxy? + multistate(data, data->conn->bits.tunnel_proxy? CURLM_STATE_WAITPROXYCONNECT: CURLM_STATE_SENDPROTOCONNECT); } @@ -1624,7 +1627,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; case CURLM_STATE_SENDPROTOCONNECT: - result = Curl_protocol_connect(data->easy_conn, &protocol_connect); + result = Curl_protocol_connect(data->conn, &protocol_connect); if(!result && !protocol_connect) /* switch to waiting state */ multistate(data, CURLM_STATE_PROTOCONNECT); @@ -1637,14 +1640,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, else if(result) { /* failure detected */ Curl_posttransfer(data); - multi_done(&data->easy_conn, result, TRUE); + multi_done(data, result, TRUE); stream_error = TRUE; } break; case CURLM_STATE_PROTOCONNECT: /* protocol-specific connect phase */ - result = Curl_protocol_connecting(data->easy_conn, &protocol_connect); + result = Curl_protocol_connecting(data->conn, &protocol_connect); if(!result && protocol_connect) { /* after the connect has completed, go WAITDO or DO */ multistate(data, Curl_pipeline_wanted(multi, CURLPIPE_HTTP1)? @@ -1654,14 +1657,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, else if(result) { /* failure detected */ Curl_posttransfer(data); - multi_done(&data->easy_conn, result, TRUE); + multi_done(data, result, TRUE); stream_error = TRUE; } break; case CURLM_STATE_WAITDO: /* Wait for our turn to DO when we're pipelining requests */ - if(Curl_pipeline_checkget_write(data, data->easy_conn)) { + if(Curl_pipeline_checkget_write(data, data->conn)) { /* Grabbed the channel */ multistate(data, CURLM_STATE_DO); rc = CURLM_CALL_MULTI_PERFORM; @@ -1671,16 +1674,16 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case CURLM_STATE_DO: if(data->set.connect_only) { /* keep connection open for application to use the socket */ - connkeep(data->easy_conn, "CONNECT_ONLY"); + connkeep(data->conn, "CONNECT_ONLY"); multistate(data, CURLM_STATE_DONE); result = CURLE_OK; rc = CURLM_CALL_MULTI_PERFORM; } else { /* Perform the protocol's DO action */ - result = multi_do(&data->easy_conn, &dophase_done); + result = multi_do(data, &dophase_done); - /* When multi_do() returns failure, data->easy_conn might be NULL! */ + /* When multi_do() returns failure, data->conn might be NULL! */ if(!result) { if(!dophase_done) { @@ -1689,7 +1692,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, struct WildcardData *wc = &data->wildcard; if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) { /* skip some states if it is important */ - multi_done(&data->easy_conn, CURLE_OK, FALSE); + multi_done(data, CURLE_OK, FALSE); multistate(data, CURLM_STATE_DONE); rc = CURLM_CALL_MULTI_PERFORM; break; @@ -1702,7 +1705,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } /* after DO, go DO_DONE... or DO_MORE */ - else if(data->easy_conn->bits.do_more) { + else if(data->conn->bits.do_more) { /* we're supposed to do more, but we need to sit down, relax and wait a little while first */ multistate(data, CURLM_STATE_DO_MORE); @@ -1715,7 +1718,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } } else if((CURLE_SEND_ERROR == result) && - data->easy_conn->bits.reuse) { + data->conn->bits.reuse) { /* * In this situation, a connection that we were trying to use * may have unexpectedly died. If possible, send the connection @@ -1725,7 +1728,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, followtype follow = FOLLOW_NONE; CURLcode drc; - drc = Curl_retry_request(data->easy_conn, &newurl); + drc = Curl_retry_request(data->conn, &newurl); if(drc) { /* a failure here pretty much implies an out of memory */ result = drc; @@ -1733,7 +1736,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } Curl_posttransfer(data); - drc = multi_done(&data->easy_conn, result, FALSE); + drc = multi_done(data, result, FALSE); /* When set to retry the connection, we must to go back to * the CONNECT state */ @@ -1765,8 +1768,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, else { /* failure detected */ Curl_posttransfer(data); - if(data->easy_conn) - multi_done(&data->easy_conn, result, FALSE); + if(data->conn) + multi_done(data, result, FALSE); stream_error = TRUE; } } @@ -1774,12 +1777,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case CURLM_STATE_DOING: /* we continue DOING until the DO phase is complete */ - result = Curl_protocol_doing(data->easy_conn, + result = Curl_protocol_doing(data->conn, &dophase_done); if(!result) { if(dophase_done) { /* after DO, go DO_DONE or DO_MORE */ - multistate(data, data->easy_conn->bits.do_more? + multistate(data, data->conn->bits.do_more? CURLM_STATE_DO_MORE: CURLM_STATE_DO_DONE); rc = CURLM_CALL_MULTI_PERFORM; @@ -1788,7 +1791,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, else { /* failure detected */ Curl_posttransfer(data); - multi_done(&data->easy_conn, result, FALSE); + multi_done(data, result, FALSE); stream_error = TRUE; } break; @@ -1797,7 +1800,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* * When we are connected, DO MORE and then go DO_DONE */ - result = multi_do_more(data->easy_conn, &control); + result = multi_do_more(data->conn, &control); /* No need to remove this handle from the send pipeline here since that is done in multi_done() */ @@ -1817,27 +1820,27 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, else { /* failure detected */ Curl_posttransfer(data); - multi_done(&data->easy_conn, result, FALSE); + multi_done(data, result, FALSE); stream_error = TRUE; } break; case CURLM_STATE_DO_DONE: /* Move ourselves from the send to recv pipeline */ - Curl_move_handle_from_send_to_recv_pipe(data, data->easy_conn); + Curl_move_handle_from_send_to_recv_pipe(data, data->conn); - if(data->easy_conn->bits.multiplex || data->easy_conn->send_pipe.size) + if(data->conn->bits.multiplex || data->conn->send_pipe.size) /* Check if we can move pending requests to send pipe */ process_pending_handles(multi); /* pipelined/multiplexed */ /* Only perform the transfer if there's a good socket to work with. Having both BAD is a signal to skip immediately to DONE */ - if((data->easy_conn->sockfd != CURL_SOCKET_BAD) || - (data->easy_conn->writesockfd != CURL_SOCKET_BAD)) + if((data->conn->sockfd != CURL_SOCKET_BAD) || + (data->conn->writesockfd != CURL_SOCKET_BAD)) multistate(data, CURLM_STATE_WAITPERFORM); else { if(data->state.wildcardmatch && - ((data->easy_conn->handler->flags & PROTOPT_WILDCARD) == 0)) { + ((data->conn->handler->flags & PROTOPT_WILDCARD) == 0)) { data->wildcard.state = CURLWC_DONE; } multistate(data, CURLM_STATE_DONE); @@ -1847,7 +1850,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case CURLM_STATE_WAITPERFORM: /* Wait for our turn to PERFORM */ - if(Curl_pipeline_checkget_read(data, data->easy_conn)) { + if(Curl_pipeline_checkget_read(data, data->conn)) { /* Grabbed the channel */ multistate(data, CURLM_STATE_PERFORM); rc = CURLM_CALL_MULTI_PERFORM; @@ -1856,7 +1859,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, case CURLM_STATE_TOOFAST: /* limit-rate exceeded in either direction */ /* if both rates are within spec, resume transfer */ - if(Curl_pgrsUpdate(data->easy_conn)) + if(Curl_pgrsUpdate(data->conn)) result = CURLE_ABORTED_BY_CALLBACK; else result = Curl_speedcheck(data, now); @@ -1926,24 +1929,24 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } /* read/write data if it is ready to do so */ - result = Curl_readwrite(data->easy_conn, data, &done, &comeback); + result = Curl_readwrite(data->conn, data, &done, &comeback); k = &data->req; if(!(k->keepon & KEEP_RECV)) /* We're done receiving */ - Curl_pipeline_leave_read(data->easy_conn); + Curl_pipeline_leave_read(data->conn); if(!(k->keepon & KEEP_SEND)) /* We're done sending */ - Curl_pipeline_leave_write(data->easy_conn); + Curl_pipeline_leave_write(data->conn); if(done || (result == CURLE_RECV_ERROR)) { /* If CURLE_RECV_ERROR happens early enough, we assume it was a race * condition and the server closed the re-used connection exactly when * we wanted to use it, so figure out if that is indeed the case. */ - CURLcode ret = Curl_retry_request(data->easy_conn, &newurl); + CURLcode ret = Curl_retry_request(data->conn, &newurl); if(!ret) retry = (newurl)?TRUE:FALSE; else if(!result) @@ -1957,8 +1960,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } } else if((CURLE_HTTP2_STREAM == result) && - Curl_h2_http_1_1_error(data->easy_conn)) { - CURLcode ret = Curl_retry_request(data->easy_conn, &newurl); + Curl_h2_http_1_1_error(data->conn)) { + CURLcode ret = Curl_retry_request(data->conn, &newurl); infof(data, "Forcing HTTP/1.1 for NTLM"); data->set.httpversion = CURL_HTTP_VERSION_1_1; @@ -1985,12 +1988,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, * happened in the data connection. */ - if(!(data->easy_conn->handler->flags & PROTOPT_DUAL) && + if(!(data->conn->handler->flags & PROTOPT_DUAL) && result != CURLE_HTTP2_STREAM) - streamclose(data->easy_conn, "Transfer returned error"); + streamclose(data->conn, "Transfer returned error"); Curl_posttransfer(data); - multi_done(&data->easy_conn, result, TRUE); + multi_done(data, result, TRUE); } else if(done) { followtype follow = FOLLOW_NONE; @@ -1999,11 +2002,11 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, Curl_posttransfer(data); /* we're no longer receiving */ - Curl_removeHandleFromPipeline(data, &data->easy_conn->recv_pipe); + Curl_removeHandleFromPipeline(data, &data->conn->recv_pipe); /* expire the new receiving pipeline head */ - if(data->easy_conn->recv_pipe.head) - Curl_expire(data->easy_conn->recv_pipe.head->ptr, 0, EXPIRE_RUN_NOW); + if(data->conn->recv_pipe.head) + Curl_expire(data->conn->recv_pipe.head->ptr, 0, EXPIRE_RUN_NOW); /* When we follow redirects or is set to retry the connection, we must to go back to the CONNECT state */ @@ -2018,7 +2021,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else follow = FOLLOW_RETRY; - result = multi_done(&data->easy_conn, CURLE_OK, FALSE); + result = multi_done(data, CURLE_OK, FALSE); if(!result) { result = Curl_follow(data, newurl, follow); if(!result) { @@ -2041,7 +2044,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, free(newurl); if(result) { stream_error = TRUE; - result = multi_done(&data->easy_conn, result, TRUE); + result = multi_done(data, result, TRUE); } } @@ -2060,18 +2063,18 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* this state is highly transient, so run another loop after this */ rc = CURLM_CALL_MULTI_PERFORM; - if(data->easy_conn) { + if(data->conn) { CURLcode res; /* Remove ourselves from the receive pipeline, if we are there. */ - Curl_removeHandleFromPipeline(data, &data->easy_conn->recv_pipe); + Curl_removeHandleFromPipeline(data, &data->conn->recv_pipe); - if(data->easy_conn->bits.multiplex || data->easy_conn->send_pipe.size) + if(data->conn->bits.multiplex || data->conn->send_pipe.size) /* Check if we can move pending requests to connection */ process_pending_handles(multi); /* pipelined/multiplexing */ /* post-transfer command */ - res = multi_done(&data->easy_conn, result, FALSE); + res = multi_done(data, result, FALSE); /* allow a previously set error code take precedence */ if(!result) @@ -2079,12 +2082,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* * If there are other handles on the pipeline, multi_done won't set - * easy_conn to NULL. In such a case, curl_multi_remove_handle() can + * conn to NULL. In such a case, curl_multi_remove_handle() can * access free'd data, if the connection is free'd and the handle * removed before we perform the processing in CURLM_STATE_COMPLETED */ - if(data->easy_conn) - data->easy_conn = NULL; + if(data->conn) + Curl_detach_connnection(data); } if(data->state.wildcardmatch) { @@ -2126,23 +2129,23 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* Check if we can move pending requests to send pipe */ process_pending_handles(multi); /* connection */ - if(data->easy_conn) { + if(data->conn) { /* if this has a connection, unsubscribe from the pipelines */ - Curl_pipeline_leave_write(data->easy_conn); - Curl_pipeline_leave_read(data->easy_conn); - Curl_removeHandleFromPipeline(data, &data->easy_conn->send_pipe); - Curl_removeHandleFromPipeline(data, &data->easy_conn->recv_pipe); + Curl_pipeline_leave_write(data->conn); + Curl_pipeline_leave_read(data->conn); + Curl_removeHandleFromPipeline(data, &data->conn->send_pipe); + Curl_removeHandleFromPipeline(data, &data->conn->recv_pipe); if(stream_error) { /* Don't attempt to send data over a connection that timed out */ bool dead_connection = result == CURLE_OPERATION_TIMEDOUT; /* disconnect properly */ - Curl_disconnect(data, data->easy_conn, dead_connection); + Curl_disconnect(data, data->conn, dead_connection); - /* This is where we make sure that the easy_conn pointer is reset. + /* This is where we make sure that the conn pointer is reset. We don't have to do this in every case block above where a failure is detected */ - data->easy_conn = NULL; + Curl_detach_connnection(data); } } else if(data->mstate == CURLM_STATE_CONNECT) { @@ -2154,11 +2157,11 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, rc = CURLM_CALL_MULTI_PERFORM; } /* if there's still a connection to use, call the progress function */ - else if(data->easy_conn && Curl_pgrsUpdate(data->easy_conn)) { + else if(data->conn && Curl_pgrsUpdate(data->conn)) { /* aborted due to progress callback return code must close the connection */ result = CURLE_ABORTED_BY_CALLBACK; - streamclose(data->easy_conn, "Aborted by callback"); + streamclose(data->conn, "Aborted by callback"); /* if not yet in DONE state, go there, otherwise COMPLETED */ multistate(data, (data->mstate < CURLM_STATE_DONE)? @@ -2181,7 +2184,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, msg->extmsg.data.result = result; rc = multi_addmsg(multi, msg); - DEBUGASSERT(!data->easy_conn); + DEBUGASSERT(!data->conn); } multistate(data, CURLM_STATE_MSGSENT); } @@ -2261,9 +2264,9 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) data = multi->easyp; while(data) { nextdata = data->next; - if(!data->state.done && data->easy_conn) + if(!data->state.done && data->conn) /* if DONE was never called for this handle */ - (void)multi_done(&data->easy_conn, CURLE_OK, TRUE); + (void)multi_done(data, CURLE_OK, TRUE); if(data->dns.hostcachetype == HCACHE_MULTI) { /* clear out the usage of the shared DNS cache */ Curl_hostcache_clean(data, data->dns.hostcache); @@ -2356,6 +2359,9 @@ static CURLMcode singlesocket(struct Curl_multi *multi, curl_socket_t s; int num; unsigned int curraction; + int actions[MAX_SOCKSPEREASYHANDLE]; + unsigned int comboaction; + bool sincebefore = FALSE; for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) socks[i] = CURL_SOCKET_BAD; @@ -2372,7 +2378,8 @@ static CURLMcode singlesocket(struct Curl_multi *multi, for(i = 0; (i< MAX_SOCKSPEREASYHANDLE) && (curraction & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i))); i++) { - int action = CURL_POLL_NONE; + unsigned int action = CURL_POLL_NONE; + unsigned int prevaction = 0; s = socks[i]; @@ -2384,29 +2391,70 @@ static CURLMcode singlesocket(struct Curl_multi *multi, if(curraction & GETSOCK_WRITESOCK(i)) action |= CURL_POLL_OUT; + actions[i] = action; if(entry) { - /* yeps, already present so check if it has the same action set */ - if(entry->action == action) - /* same, continue */ - continue; + /* check if new for this transfer */ + for(i = 0; i< data->numsocks; i++) { + if(s == data->sockets[i]) { + prevaction = data->actions[i]; + sincebefore = TRUE; + break; + } + } + } else { - /* this is a socket we didn't have before, add it! */ - entry = sh_addentry(&multi->sockhash, s, data); + /* this is a socket we didn't have before, add it to the hash! */ + entry = sh_addentry(&multi->sockhash, s); if(!entry) /* fatal */ return CURLM_OUT_OF_MEMORY; } + if(sincebefore && (prevaction != action)) { + /* Socket was used already, but different action now */ + if(prevaction & CURL_POLL_IN) + entry->readers--; + if(prevaction & CURL_POLL_OUT) + entry->writers--; + if(action & CURL_POLL_IN) + entry->readers++; + if(action & CURL_POLL_OUT) + entry->writers++; + } + else if(!sincebefore) { + /* a new user */ + entry->users++; + if(action & CURL_POLL_IN) + entry->readers++; + if(action & CURL_POLL_OUT) + entry->writers++; + + /* add 'data' to the list of handles using this socket! */ + Curl_llist_insert_next(&entry->list, entry->list.tail, + data, &data->sh_queue); + } + + comboaction = (entry->writers? CURL_POLL_OUT : 0) | + (entry->readers ? CURL_POLL_IN : 0); + +#if 0 + infof(data, "--- Comboaction: %u readers %u writers\n", + entry->readers, entry->writers); +#endif + /* check if it has the same action set */ + if(entry->action == comboaction) + /* same, continue */ + continue; /* we know (entry != NULL) at this point, see the logic above */ if(multi->socket_cb) multi->socket_cb(data, s, - action, + comboaction, multi->socket_userp, entry->socketp); - entry->action = action; /* store the current action state */ + entry->action = comboaction; /* store the current action state */ } num = i; /* number of sockets */ @@ -2415,73 +2463,45 @@ static CURLMcode singlesocket(struct Curl_multi *multi, make sure to detect sockets that are removed */ for(i = 0; i< data->numsocks; i++) { int j; + bool stillused = FALSE; s = data->sockets[i]; - for(j = 0; j<num; j++) { + for(j = 0; j < num; j++) { if(s == socks[j]) { /* this is still supervised */ - s = CURL_SOCKET_BAD; + stillused = TRUE; break; } } + if(stillused) + continue; entry = sh_getentry(&multi->sockhash, s); + /* if this is NULL here, the socket has been closed and notified so + already by Curl_multi_closed() */ if(entry) { - /* this socket has been removed. Tell the app to remove it */ - bool remove_sock_from_hash = TRUE; - - /* check if the socket to be removed serves a connection which has - other easy-s in a pipeline. In this case the socket should not be - removed. */ - struct connectdata *easy_conn = data->easy_conn; - if(easy_conn) { - if(easy_conn->recv_pipe.size > 1) { - /* the handle should not be removed from the pipe yet */ - remove_sock_from_hash = FALSE; - - /* Update the sockhash entry to instead point to the next in line - for the recv_pipe, or the first (in case this particular easy - isn't already) */ - if(entry->easy == data) { - if(Curl_recvpipe_head(data, easy_conn)) - entry->easy = easy_conn->recv_pipe.head->next->ptr; - else - entry->easy = easy_conn->recv_pipe.head->ptr; - } - } - if(easy_conn->send_pipe.size > 1) { - /* the handle should not be removed from the pipe yet */ - remove_sock_from_hash = FALSE; - - /* Update the sockhash entry to instead point to the next in line - for the send_pipe, or the first (in case this particular easy - isn't already) */ - if(entry->easy == data) { - if(Curl_sendpipe_head(data, easy_conn)) - entry->easy = easy_conn->send_pipe.head->next->ptr; - else - entry->easy = easy_conn->send_pipe.head->ptr; - } - } - /* Don't worry about overwriting recv_pipe head with send_pipe_head, - when action will be asked on the socket (see multi_socket()), the - head of the correct pipe will be taken according to the - action. */ - } - - if(remove_sock_from_hash) { - /* in this case 'entry' is always non-NULL */ + int oldactions = data->actions[i]; + /* this socket has been removed. Decrease user count */ + entry->users--; + if(oldactions & CURL_POLL_OUT) + entry->writers--; + if(oldactions & CURL_POLL_IN) + entry->readers--; + if(!entry->users) { if(multi->socket_cb) - multi->socket_cb(data, - s, - CURL_POLL_REMOVE, + multi->socket_cb(data, s, CURL_POLL_REMOVE, multi->socket_userp, entry->socketp); sh_delentry(&multi->sockhash, s); } - } /* if sockhash entry existed */ + else { + /* remove this transfer as a user of this socket */ + Curl_llist_remove(&entry->list, &data->sh_queue, NULL); + } + } } /* for loop over numsocks */ memcpy(data->sockets, socks, num*sizeof(curl_socket_t)); + memcpy(data->actions, actions, num*sizeof(int)); data->numsocks = num; return CURLM_OK; } @@ -2621,46 +2641,50 @@ static CURLMcode multi_socket(struct Curl_multi *multi, and just move on. */ ; else { + struct curl_llist *list = &entry->list; + struct curl_llist_element *e; SIGPIPE_VARIABLE(pipe_st); - data = entry->easy; - - if(data->magic != CURLEASY_MAGIC_NUMBER) - /* bad bad bad bad bad bad bad */ - return CURLM_INTERNAL_ERROR; - - /* If the pipeline is enabled, take the handle which is in the head of - the pipeline. If we should write into the socket, take the send_pipe - head. If we should read from the socket, take the recv_pipe head. */ - if(data->easy_conn) { - if((ev_bitmask & CURL_POLL_OUT) && - data->easy_conn->send_pipe.head) - data = data->easy_conn->send_pipe.head->ptr; - else if((ev_bitmask & CURL_POLL_IN) && - data->easy_conn->recv_pipe.head) - data = data->easy_conn->recv_pipe.head->ptr; - } + /* the socket can be shared by many transfers, iterate */ + for(e = list->head; e; e = e->next) { + data = (struct Curl_easy *)e->ptr; + + if(data->magic != CURLEASY_MAGIC_NUMBER) + /* bad bad bad bad bad bad bad */ + return CURLM_INTERNAL_ERROR; + + /* If the pipeline is enabled, take the handle which is in the head of + the pipeline. If we should write into the socket, take the + send_pipe head. If we should read from the socket, take the + recv_pipe head. */ + if(data->conn) { + if((ev_bitmask & CURL_POLL_OUT) && + data->conn->send_pipe.head) + data = data->conn->send_pipe.head->ptr; + else if((ev_bitmask & CURL_POLL_IN) && + data->conn->recv_pipe.head) + data = data->conn->recv_pipe.head->ptr; + } - if(data->easy_conn && - !(data->easy_conn->handler->flags & PROTOPT_DIRLOCK)) - /* set socket event bitmask if they're not locked */ - data->easy_conn->cselect_bits = ev_bitmask; + if(data->conn && !(data->conn->handler->flags & PROTOPT_DIRLOCK)) + /* set socket event bitmask if they're not locked */ + data->conn->cselect_bits = ev_bitmask; - sigpipe_ignore(data, &pipe_st); - result = multi_runsingle(multi, now, data); - sigpipe_restore(&pipe_st); + sigpipe_ignore(data, &pipe_st); + result = multi_runsingle(multi, now, data); + sigpipe_restore(&pipe_st); - if(data->easy_conn && - !(data->easy_conn->handler->flags & PROTOPT_DIRLOCK)) - /* clear the bitmask only if not locked */ - data->easy_conn->cselect_bits = 0; + if(data->conn && !(data->conn->handler->flags & PROTOPT_DIRLOCK)) + /* clear the bitmask only if not locked */ + data->conn->cselect_bits = 0; - if(CURLM_OK >= result) { - /* get the socket(s) and check if the state has been changed since - last */ - result = singlesocket(multi, data); - if(result) - return result; + if(CURLM_OK >= result) { + /* get the socket(s) and check if the state has been changed since + last */ + result = singlesocket(multi, data); + if(result) + return result; + } } /* Now we fall-through and do the timer-based stuff, since we don't want @@ -3004,6 +3028,9 @@ void Curl_expire(struct Curl_easy *data, time_t milli, expire_id id) DEBUGASSERT(id < EXPIRE_LAST); + infof(data, "Expire in %ld ms for %x (transfer %p)\n", + (long)milli, id, data); + set = Curl_now(); set.tv_sec += milli/1000; set.tv_usec += (unsigned int)(milli%1000)*1000; @@ -3095,7 +3122,7 @@ void Curl_expire_clear(struct Curl_easy *data) } #ifdef DEBUGBUILD - infof(data, "Expire cleared\n"); + infof(data, "Expire cleared (transfer %p)\n", data); #endif nowp->tv_sec = 0; nowp->tv_usec = 0; |