diff options
Diffstat (limited to 'lib/multi.c')
-rw-r--r-- | lib/multi.c | 860 |
1 files changed, 445 insertions, 415 deletions
diff --git a/lib/multi.c b/lib/multi.c index 4cc7c5ae6..f307d63b9 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -69,7 +69,7 @@ #define CURL_MULTI_HANDLE 0x000bab1e #define GOOD_MULTI_HANDLE(x) \ - ((x) && (x)->type == CURL_MULTI_HANDLE) + ((x) && (x)->magic == CURL_MULTI_HANDLE) static CURLMcode singlesocket(struct Curl_multi *multi, struct Curl_easy *data); @@ -83,19 +83,19 @@ static void process_pending_handles(struct Curl_multi *multi); #ifdef DEBUGBUILD static const char * const statename[]={ "INIT", - "CONNECT_PEND", + "PENDING", "CONNECT", - "WAITRESOLVE", - "WAITCONNECT", - "WAITPROXYCONNECT", - "SENDPROTOCONNECT", + "RESOLVING", + "CONNECTING", + "TUNNELING", "PROTOCONNECT", + "PROTOCONNECTING", "DO", "DOING", - "DO_MORE", - "DO_DONE", - "PERFORM", - "TOOFAST", + "DOING_MORE", + "DID", + "PERFORMING", + "RATELIMITING", "DONE", "COMPLETED", "MSGSENT", @@ -105,7 +105,14 @@ static const char * const statename[]={ /* function pointer called once when switching TO a state */ typedef void (*init_multistate_func)(struct Curl_easy *data); -static void Curl_init_completed(struct Curl_easy *data) +/* called in DID state, before PERFORMING state */ +static void before_perform(struct Curl_easy *data) +{ + data->req.chunk = FALSE; + Curl_pgrsTime(data, TIMER_PRETRANSFER); +} + +static void init_completed(struct Curl_easy *data) { /* this is a completed transfer */ @@ -123,23 +130,23 @@ static void mstate(struct Curl_easy *data, CURLMstate state ) { CURLMstate oldstate = data->mstate; - static const init_multistate_func finit[CURLM_STATE_LAST] = { + static const init_multistate_func finit[MSTATE_LAST] = { NULL, /* INIT */ - NULL, /* CONNECT_PEND */ + NULL, /* PENDING */ Curl_init_CONNECT, /* CONNECT */ - NULL, /* WAITRESOLVE */ - NULL, /* WAITCONNECT */ - NULL, /* WAITPROXYCONNECT */ - NULL, /* SENDPROTOCONNECT */ + NULL, /* RESOLVING */ + NULL, /* CONNECTING */ + NULL, /* TUNNELING */ NULL, /* PROTOCONNECT */ + NULL, /* PROTOCONNECTING */ Curl_connect_free, /* DO */ NULL, /* DOING */ - NULL, /* DO_MORE */ - NULL, /* DO_DONE */ - NULL, /* PERFORM */ - NULL, /* TOOFAST */ + NULL, /* DOING_MORE */ + before_perform, /* DID */ + NULL, /* PERFORMING */ + NULL, /* RATELIMITING */ NULL, /* DONE */ - Curl_init_completed, /* COMPLETED */ + init_completed, /* COMPLETED */ NULL /* MSGSENT */ }; @@ -154,21 +161,21 @@ static void mstate(struct Curl_easy *data, CURLMstate state data->mstate = state; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - if(data->mstate >= CURLM_STATE_CONNECT_PEND && - data->mstate < CURLM_STATE_COMPLETED) { + if(data->mstate >= MSTATE_PENDING && + data->mstate < MSTATE_COMPLETED) { long connection_id = -5000; if(data->conn) connection_id = data->conn->connection_id; infof(data, - "STATE: %s => %s handle %p; line %d (connection #%ld)\n", + "STATE: %s => %s handle %p; line %d (connection #%ld)", statename[oldstate], statename[data->mstate], (void *)data, lineno, connection_id); } #endif - if(state == CURLM_STATE_COMPLETED) { + if(state == MSTATE_COMPLETED) { /* changing to COMPLETED means there's one less easy handle 'alive' */ DEBUGASSERT(data->multi->num_alive > 0); data->multi->num_alive--; @@ -193,8 +200,8 @@ struct Curl_sh_entry { struct Curl_hash transfers; /* hash of transfers 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 */ + void *socketp; /* settable by users with curl_multi_assign() */ unsigned int readers; /* this many transfers want to read */ unsigned int writers; /* this many transfers want to write */ }; @@ -353,7 +360,7 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ if(!multi) return NULL; - multi->type = CURL_MULTI_HANDLE; + multi->magic = CURL_MULTI_HANDLE; if(Curl_mk_dnscache(&multi->hostcache)) goto error; @@ -446,7 +453,7 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, data->set.errorbuffer[0] = 0; /* set the easy handle */ - multistate(data, CURLM_STATE_INIT); + multistate(data, MSTATE_INIT); /* for multi interface connections, we share DNS cache automatically if the easy handle's one is currently not set. */ @@ -555,16 +562,14 @@ static CURLcode multi_done(struct Curl_easy *data, struct connectdata *conn = data->conn; unsigned int i; - DEBUGF(infof(data, "multi_done\n")); + DEBUGF(infof(data, "multi_done")); if(data->state.done) /* Stop if multi_done() has already been called */ return CURLE_OK; - conn->data = data; /* ensure the connection uses this transfer now */ - /* Stop the resolver and free its own resources (but not dns_entry yet). */ - Curl_resolver_kill(conn); + Curl_resolver_kill(data); /* Cleanup possible redirect junk */ Curl_safefree(data->req.newurl); @@ -585,14 +590,14 @@ static CURLcode multi_done(struct Curl_easy *data, /* this calls the protocol-specific function pointer previously set */ if(conn->handler->done) - result = conn->handler->done(conn, status, premature); + result = conn->handler->done(data, status, premature); else result = status; if(CURLE_ABORTED_BY_CALLBACK != result) { /* avoid this if we already aborted by callback to avoid this calling another callback */ - CURLcode rc = Curl_pgrsDone(conn); + CURLcode rc = Curl_pgrsDone(data); if(!result && rc) result = CURLE_ABORTED_BY_CALLBACK; } @@ -603,16 +608,13 @@ static CURLcode multi_done(struct Curl_easy *data, Curl_detach_connnection(data); if(CONN_INUSE(conn)) { /* Stop if still used. */ - /* conn->data must not remain pointing to this transfer since it is going - away! Find another to own it! */ - conn->data = conn->easyq.head->ptr; CONNCACHE_UNLOCK(data); DEBUGF(infof(data, "Connection still in use %zu, " - "no more multi_done now!\n", + "no more multi_done now!", conn->easyq.size)); return CURLE_OK; } - conn->data = NULL; /* the connection now has no owner */ + data->state.done = TRUE; /* called just now! */ if(conn->dns_entry) { @@ -685,7 +687,7 @@ static CURLcode multi_done(struct Curl_easy *data, if(Curl_conncache_return_conn(data, conn)) { /* remember the most recently used connection */ data->state.lastconnect_id = conn->connection_id; - infof(data, "%s\n", buffer); + infof(data, "%s", buffer); } else data->state.lastconnect_id = -1; @@ -696,22 +698,17 @@ static CURLcode multi_done(struct Curl_easy *data, return result; } -static int close_connect_only(struct connectdata *conn, void *param) +static int close_connect_only(struct Curl_easy *data, + struct connectdata *conn, void *param) { - struct Curl_easy *data = param; - + (void)param; if(data->state.lastconnect_id != conn->connection_id) return 0; - if(conn->data != data) - return 1; - conn->data = NULL; - if(!conn->bits.connect_only) return 1; connclose(conn, "Removing connect-only easy handle"); - conn->bits.connect_only = FALSE; return 1; } @@ -721,7 +718,6 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, { struct Curl_easy *easy = data; bool premature; - bool easy_owns_conn; struct Curl_llist_element *e; /* First, make some basic checks that the CURLM handle is a good handle */ @@ -743,9 +739,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - premature = (data->mstate < CURLM_STATE_COMPLETED) ? TRUE : FALSE; - easy_owns_conn = (data->conn && (data->conn->data == easy)) ? - TRUE : FALSE; + premature = (data->mstate < MSTATE_COMPLETED) ? TRUE : FALSE; /* If the 'state' is not INIT or COMPLETED, we might need to do something nice to put the easy_handle in a good known state when this returns. */ @@ -756,28 +750,20 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, } if(data->conn && - data->mstate > CURLM_STATE_DO && - data->mstate < CURLM_STATE_COMPLETED) { + data->mstate > MSTATE_DO && + data->mstate < MSTATE_COMPLETED) { /* Set connection owner so that the DONE function closes it. We can safely do this here since connection is killed. */ - data->conn->data = easy; streamclose(data->conn, "Removed with partial response"); - easy_owns_conn = TRUE; } if(data->conn) { + /* multi_done() clears the association between the easy handle and the + connection. - /* we must call multi_done() here (if we still own the connection) so that - we don't leave a half-baked one around */ - if(easy_owns_conn) { - - /* multi_done() clears the association between the easy handle and the - connection. - - Note that this ignores the return code simply because there's - nothing really useful to do with it anyway! */ - (void)multi_done(data, data->result, premature); - } + Note that this ignores the return code simply because there's + nothing really useful to do with it anyway! */ + (void)multi_done(data, data->result, premature); } /* The timer must be shut down before data->multi is set to NULL, else the @@ -805,7 +791,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, /* change state without using multistate(), only to make singlesocket() do what we want */ - data->mstate = CURLM_STATE_COMPLETED; + data->mstate = MSTATE_COMPLETED; singlesocket(multi, easy); /* to let the application know what sockets that vanish with this handle */ @@ -815,7 +801,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, if(data->state.lastconnect_id != -1) { /* Mark any connect-only connection for closure */ Curl_conncache_foreach(data, data->state.conn_cache, - data, &close_connect_only); + NULL, close_connect_only); } #ifdef USE_LIBPSL @@ -843,6 +829,17 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, } } + /* Remove from the pending list if it is there. Otherwise this will + remain on the pending list forever due to the state change. */ + for(e = multi->pending.head; e; e = e->next) { + struct Curl_easy *curr_data = e->ptr; + + if(curr_data == data) { + Curl_llist_remove(&multi->pending, e, NULL); + break; + } + } + /* make the previous node point to our next */ if(data->prev) data->prev->next = data->next; @@ -859,6 +856,8 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, We do not touch the easy handle here! */ multi->num_easy--; /* one less to care about now */ + process_pending_handles(multi); + Curl_update_timer(multi); return CURLM_OK; } @@ -878,8 +877,10 @@ bool Curl_multiplex_wanted(const struct Curl_multi *multi) void Curl_detach_connnection(struct Curl_easy *data) { struct connectdata *conn = data->conn; - if(conn) + if(conn) { Curl_llist_remove(&conn->easyq, &data->conn_queue, NULL); + Curl_ssl_detach_conn(data, conn); + } data->conn = NULL; } @@ -896,6 +897,9 @@ void Curl_attach_connnection(struct Curl_easy *data, data->conn = conn; Curl_llist_insert_next(&conn->easyq, conn->easyq.tail, data, &data->conn_queue); + if(conn->handler->attach) + conn->handler->attach(data, conn); + Curl_ssl_associate_conn(data, conn); } static int waitconnect_getsock(struct connectdata *conn, @@ -908,7 +912,7 @@ static int waitconnect_getsock(struct connectdata *conn, #ifdef USE_SSL #ifndef CURL_DISABLE_PROXY if(CONNECT_FIRSTSOCKET_PROXY_SSL()) - return Curl_ssl_getsock(conn, sock); + return Curl_ssl->getsock(conn, sock); #endif #endif @@ -936,35 +940,36 @@ static int waitproxyconnect_getsock(struct connectdata *conn, { sock[0] = conn->sock[FIRSTSOCKET]; - /* when we've sent a CONNECT to a proxy, we should rather wait for the - socket to become readable to be able to get the response headers */ if(conn->connect_state) - return GETSOCK_READSOCK(0); + return Curl_connect_getsock(conn); return GETSOCK_WRITESOCK(0); } -static int domore_getsock(struct connectdata *conn, +static int domore_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) { if(conn && conn->handler->domore_getsock) - return conn->handler->domore_getsock(conn, socks); + return conn->handler->domore_getsock(data, conn, socks); return GETSOCK_BLANK; } -static int doing_getsock(struct connectdata *conn, +static int doing_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) { if(conn && conn->handler->doing_getsock) - return conn->handler->doing_getsock(conn, socks); + return conn->handler->doing_getsock(data, conn, socks); return GETSOCK_BLANK; } -static int protocol_getsock(struct connectdata *conn, +static int protocol_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) { if(conn->handler->proto_getsock) - return conn->handler->proto_getsock(conn, socks); + return conn->handler->proto_getsock(data, conn, socks); /* Backup getsock logic. Since there is a live socket in use, we must wait for it or it will be removed from watching when the multi_socket API is used. */ @@ -977,47 +982,41 @@ static int protocol_getsock(struct connectdata *conn, static int multi_getsock(struct Curl_easy *data, curl_socket_t *socks) { + struct connectdata *conn = data->conn; /* The no connection case can happen when this is called from curl_multi_remove_handle() => singlesocket() => multi_getsock(). */ - if(!data->conn) + if(!conn) return 0; - if(data->mstate > CURLM_STATE_CONNECT && - data->mstate < CURLM_STATE_COMPLETED) { - /* Set up ownership correctly */ - data->conn->data = data; - } - switch(data->mstate) { default: return 0; - case CURLM_STATE_WAITRESOLVE: - return Curl_resolv_getsock(data->conn, socks); + case MSTATE_RESOLVING: + return Curl_resolv_getsock(data, socks); - case CURLM_STATE_PROTOCONNECT: - case CURLM_STATE_SENDPROTOCONNECT: - return protocol_getsock(data->conn, socks); + case MSTATE_PROTOCONNECTING: + case MSTATE_PROTOCONNECT: + return protocol_getsock(data, conn, socks); - case CURLM_STATE_DO: - case CURLM_STATE_DOING: - return doing_getsock(data->conn, socks); + case MSTATE_DO: + case MSTATE_DOING: + return doing_getsock(data, conn, socks); - case CURLM_STATE_WAITPROXYCONNECT: - return waitproxyconnect_getsock(data->conn, socks); + case MSTATE_TUNNELING: + return waitproxyconnect_getsock(conn, socks); - case CURLM_STATE_WAITCONNECT: - return waitconnect_getsock(data->conn, socks); + case MSTATE_CONNECTING: + return waitconnect_getsock(conn, socks); - case CURLM_STATE_DO_MORE: - return domore_getsock(data->conn, socks); + case MSTATE_DOING_MORE: + return domore_getsock(data, conn, socks); - 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: - return Curl_single_getsock(data->conn, socks); + case MSTATE_DID: /* since is set after DO is completed, we switch to + waiting for the same as the PERFORMING state */ + case MSTATE_PERFORMING: + return Curl_single_getsock(data, conn, socks); } } @@ -1043,16 +1042,27 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, data = multi->easyp; while(data) { - int bitmap = multi_getsock(data, sockbunch); + int bitmap; +#ifdef __clang_analyzer_ + /* to prevent "The left operand of '>=' is a garbage value" warnings */ + memset(sockbunch, 0, sizeof(sockbunch)); +#endif + bitmap = multi_getsock(data, sockbunch); for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) { curl_socket_t s = CURL_SOCKET_BAD; - if((bitmap & GETSOCK_READSOCK(i)) && VALID_SOCK((sockbunch[i]))) { + if((bitmap & GETSOCK_READSOCK(i)) && VALID_SOCK(sockbunch[i])) { + if(!FDSET_SOCK(sockbunch[i])) + /* pretend it doesn't exist */ + continue; FD_SET(sockbunch[i], read_fd_set); s = sockbunch[i]; } - if((bitmap & GETSOCK_WRITESOCK(i)) && VALID_SOCK((sockbunch[i]))) { + if((bitmap & GETSOCK_WRITESOCK(i)) && VALID_SOCK(sockbunch[i])) { + if(!FDSET_SOCK(sockbunch[i])) + /* pretend it doesn't exist */ + continue; FD_SET(sockbunch[i], write_fd_set); s = sockbunch[i]; } @@ -1073,13 +1083,13 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, #define NUM_POLLS_ON_STACK 10 -static CURLMcode Curl_multi_wait(struct Curl_multi *multi, - struct curl_waitfd extra_fds[], - unsigned int extra_nfds, - int timeout_ms, - int *ret, - bool extrawait, /* when no socket, wait */ - bool use_wakeup) +static CURLMcode multi_wait(struct Curl_multi *multi, + struct curl_waitfd extra_fds[], + unsigned int extra_nfds, + int timeout_ms, + int *ret, + bool extrawait, /* when no socket, wait */ + bool use_wakeup) { struct Curl_easy *data; curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; @@ -1089,15 +1099,16 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi, unsigned int curlfds; long timeout_internal; int retcode = 0; -#ifndef USE_WINSOCK struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; struct pollfd *ufds = &a_few_on_stack[0]; bool ufds_malloc = FALSE; -#else - struct pollfd pre_poll; +#ifdef USE_WINSOCK WSANETWORKEVENTS wsa_events; DEBUGASSERT(multi->wsa_event != WSA_INVALID_EVENT); #endif +#ifndef ENABLE_WAKEUP + (void)use_wakeup; +#endif if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -1116,11 +1127,11 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi, for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) { curl_socket_t s = CURL_SOCKET_BAD; - if(bitmap & GETSOCK_READSOCK(i)) { + if((bitmap & GETSOCK_READSOCK(i)) && VALID_SOCK((sockbunch[i]))) { ++nfds; s = sockbunch[i]; } - if(bitmap & GETSOCK_WRITESOCK(i)) { + if((bitmap & GETSOCK_WRITESOCK(i)) && VALID_SOCK((sockbunch[i]))) { ++nfds; s = sockbunch[i]; } @@ -1152,7 +1163,6 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi, } #endif -#ifndef USE_WINSOCK if(nfds > NUM_POLLS_ON_STACK) { /* 'nfds' is a 32 bit value and 'struct pollfd' is typically 8 bytes big, so at 2^29 sockets this value might wrap. When a process gets @@ -1163,9 +1173,7 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi, return CURLM_OUT_OF_MEMORY; ufds_malloc = TRUE; } - nfds = 0; -#endif /* only do the second loop if we found descriptors in the first stage run above */ @@ -1181,36 +1189,36 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi, #ifdef USE_WINSOCK long mask = 0; #endif - if(bitmap & GETSOCK_READSOCK(i)) { + if((bitmap & GETSOCK_READSOCK(i)) && VALID_SOCK((sockbunch[i]))) { + s = sockbunch[i]; #ifdef USE_WINSOCK - if(timeout_ms && SOCKET_READABLE(sockbunch[i], 0) > 0) - timeout_ms = 0; mask |= FD_READ|FD_ACCEPT|FD_CLOSE; -#else - ufds[nfds].fd = sockbunch[i]; +#endif + ufds[nfds].fd = s; ufds[nfds].events = POLLIN; ++nfds; -#endif - s = sockbunch[i]; } - if(bitmap & GETSOCK_WRITESOCK(i)) { + if((bitmap & GETSOCK_WRITESOCK(i)) && VALID_SOCK((sockbunch[i]))) { + s = sockbunch[i]; #ifdef USE_WINSOCK - if(timeout_ms && SOCKET_WRITABLE(sockbunch[i], 0) > 0) - timeout_ms = 0; mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; -#else - ufds[nfds].fd = sockbunch[i]; + send(s, NULL, 0, 0); /* reset FD_WRITE */ +#endif + ufds[nfds].fd = s; ufds[nfds].events = POLLOUT; ++nfds; -#endif - s = sockbunch[i]; } + /* s is only set if either being readable or writable is checked */ if(s == CURL_SOCKET_BAD) { + /* break on entry not checked for being readable or writable */ break; } #ifdef USE_WINSOCK - if(WSAEventSelect(s, multi->wsa_event, mask) != 0) + if(WSAEventSelect(s, multi->wsa_event, mask) != 0) { + if(ufds_malloc) + free(ufds); return CURLM_INTERNAL_ERROR; + } #endif } @@ -1222,35 +1230,20 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi, for(i = 0; i < extra_nfds; i++) { #ifdef USE_WINSOCK long mask = 0; - extra_fds[i].revents = 0; - pre_poll.fd = extra_fds[i].fd; - pre_poll.events = 0; - pre_poll.revents = 0; - if(extra_fds[i].events & CURL_WAIT_POLLIN) { + if(extra_fds[i].events & CURL_WAIT_POLLIN) mask |= FD_READ|FD_ACCEPT|FD_CLOSE; - pre_poll.events |= POLLIN; - } - if(extra_fds[i].events & CURL_WAIT_POLLPRI) { + if(extra_fds[i].events & CURL_WAIT_POLLPRI) mask |= FD_OOB; - pre_poll.events |= POLLPRI; - } if(extra_fds[i].events & CURL_WAIT_POLLOUT) { mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; - pre_poll.events |= POLLOUT; + send(extra_fds[i].fd, NULL, 0, 0); /* reset FD_WRITE */ } - if(Curl_poll(&pre_poll, 1, 0) > 0) { - if(pre_poll.revents & POLLIN) - extra_fds[i].revents |= CURL_WAIT_POLLIN; - if(pre_poll.revents & POLLPRI) - extra_fds[i].revents |= CURL_WAIT_POLLPRI; - if(pre_poll.revents & POLLOUT) - extra_fds[i].revents |= CURL_WAIT_POLLOUT; - if(extra_fds[i].revents) - timeout_ms = 0; - } - if(WSAEventSelect(extra_fds[i].fd, multi->wsa_event, mask) != 0) + if(WSAEventSelect(extra_fds[i].fd, multi->wsa_event, mask) != 0) { + if(ufds_malloc) + free(ufds); return CURLM_INTERNAL_ERROR; -#else + } +#endif ufds[nfds].fd = extra_fds[i].fd; ufds[nfds].events = 0; if(extra_fds[i].events & CURL_WAIT_POLLIN) @@ -1260,7 +1253,6 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi, if(extra_fds[i].events & CURL_WAIT_POLLOUT) ufds[nfds].events |= POLLOUT; ++nfds; -#endif } #ifdef ENABLE_WAKEUP @@ -1273,53 +1265,59 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi, #endif #endif +#if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK) + if(nfds || use_wakeup) { +#else if(nfds) { - /* wait... */ +#endif + int pollrc; #ifdef USE_WINSOCK - WSAWaitForMultipleEvents(1, &multi->wsa_event, FALSE, timeout_ms, FALSE); + if(nfds) + pollrc = Curl_poll(ufds, nfds, 0); /* just pre-check with WinSock */ + else + pollrc = 0; + if(pollrc <= 0) /* now wait... if not ready during the pre-check above */ + WSAWaitForMultipleEvents(1, &multi->wsa_event, FALSE, timeout_ms, FALSE); #else - int pollrc = Curl_poll(ufds, nfds, timeout_ms); + pollrc = Curl_poll(ufds, nfds, timeout_ms); /* wait... */ #endif -#ifdef USE_WINSOCK - /* With Winsock, we have to run this unconditionally to call - WSAEventSelect(fd, event, 0) on all the sockets */ - { - retcode = 0; -#else if(pollrc > 0) { retcode = pollrc; +#ifdef USE_WINSOCK + } + /* With WinSock, we have to run the following section unconditionally + to call WSAEventSelect(fd, event, 0) on all the sockets */ + { #endif /* copy revents results from the poll to the curl_multi_wait poll struct, the bit values of the actual underlying poll() implementation may not be the same as the ones in the public libcurl API! */ for(i = 0; i < extra_nfds; i++) { + unsigned r = ufds[curlfds + i].revents; unsigned short mask = 0; #ifdef USE_WINSOCK wsa_events.lNetworkEvents = 0; - mask = extra_fds[i].revents; - if(WSAEnumNetworkEvents(extra_fds[i].fd, multi->wsa_event, - &wsa_events) == 0) { + if(WSAEnumNetworkEvents(extra_fds[i].fd, NULL, &wsa_events) == 0) { if(wsa_events.lNetworkEvents & (FD_READ|FD_ACCEPT|FD_CLOSE)) mask |= CURL_WAIT_POLLIN; if(wsa_events.lNetworkEvents & (FD_WRITE|FD_CONNECT|FD_CLOSE)) mask |= CURL_WAIT_POLLOUT; if(wsa_events.lNetworkEvents & FD_OOB) mask |= CURL_WAIT_POLLPRI; - if(ret && wsa_events.lNetworkEvents != 0) + if(ret && pollrc <= 0 && wsa_events.lNetworkEvents) retcode++; } WSAEventSelect(extra_fds[i].fd, multi->wsa_event, 0); -#else - unsigned r = ufds[curlfds + i].revents; - + if(pollrc <= 0) + continue; +#endif if(r & POLLIN) mask |= CURL_WAIT_POLLIN; if(r & POLLOUT) mask |= CURL_WAIT_POLLOUT; if(r & POLLPRI) mask |= CURL_WAIT_POLLPRI; -#endif extra_fds[i].revents = mask; } @@ -1334,23 +1332,16 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi, for(i = 0; i < MAX_SOCKSPEREASYHANDLE; i++) { if(bitmap & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i))) { wsa_events.lNetworkEvents = 0; - if(WSAEnumNetworkEvents(sockbunch[i], multi->wsa_event, - &wsa_events) == 0) { - if(ret && wsa_events.lNetworkEvents != 0) - retcode++; - } - if(ret && !timeout_ms && wsa_events.lNetworkEvents == 0) { - if((bitmap & GETSOCK_READSOCK(i)) && - SOCKET_READABLE(sockbunch[i], 0) > 0) - retcode++; - else if((bitmap & GETSOCK_WRITESOCK(i)) && - SOCKET_WRITABLE(sockbunch[i], 0) > 0) + if(WSAEnumNetworkEvents(sockbunch[i], NULL, &wsa_events) == 0) { + if(ret && pollrc <= 0 && wsa_events.lNetworkEvents) retcode++; } WSAEventSelect(sockbunch[i], multi->wsa_event, 0); } - else + else { + /* break on entry not checked for being readable or writable */ break; + } } data = data->next; @@ -1385,16 +1376,15 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi, } } -#ifndef USE_WINSOCK if(ufds_malloc) free(ufds); -#endif if(ret) *ret = retcode; - if(!extrawait || nfds) - /* if any socket was checked */ - ; - else { +#if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK) + if(extrawait && !nfds && !use_wakeup) { +#else + if(extrawait && !nfds) { +#endif long sleep_ms = 0; /* Avoid busy-looping when there's nothing particular to wait for */ @@ -1418,8 +1408,8 @@ CURLMcode curl_multi_wait(struct Curl_multi *multi, int timeout_ms, int *ret) { - return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, FALSE, - FALSE); + return multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, FALSE, + FALSE); } CURLMcode curl_multi_poll(struct Curl_multi *multi, @@ -1428,8 +1418,8 @@ CURLMcode curl_multi_poll(struct Curl_multi *multi, int timeout_ms, int *ret) { - return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, TRUE, - TRUE); + return multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, TRUE, + TRUE); } CURLMcode curl_multi_wakeup(struct Curl_multi *multi) @@ -1460,7 +1450,7 @@ CURLMcode curl_multi_wakeup(struct Curl_multi *multi) The write socket is set to non-blocking, this way this function cannot block, making it safe to call even from the same thread - that will call Curl_multi_wait(). If swrite() returns that it + that will call curl_multi_wait(). If swrite() returns that it would block, it's considered successful because it means that previous calls to this function will wake up the poll(). */ if(swrite(multi->wakeup_pair[1], buf, sizeof(buf)) < 0) { @@ -1518,25 +1508,13 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi, Curl_init_do(data, NULL); /* take this handle to the perform state right away */ - multistate(data, CURLM_STATE_PERFORM); + multistate(data, MSTATE_PERFORMING); Curl_attach_connnection(data, conn); k->keepon |= KEEP_RECV; /* setup to receive! */ } return rc; } -/* - * do_complete is called when the DO actions are complete. - * - * We init chunking and trailer bits to their default values here immediately - * before receiving any header data for the current request. - */ -static void do_complete(struct connectdata *conn) -{ - conn->data->req.chunk = FALSE; - Curl_pgrsTime(conn->data, TIMER_PRETRANSFER); -} - static CURLcode multi_do(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; @@ -1544,16 +1522,11 @@ static CURLcode multi_do(struct Curl_easy *data, bool *done) DEBUGASSERT(conn); DEBUGASSERT(conn->handler); - DEBUGASSERT(conn->data == data); - if(conn->handler->do_it) { + if(conn->handler->do_it) /* generic protocol-specific function pointer set in curl_connect() */ - result = conn->handler->do_it(conn, done); + result = conn->handler->do_it(data, done); - if(!result && *done) - /* do_complete must be called after the protocol-specific DO function */ - do_complete(conn); - } return result; } @@ -1566,36 +1539,85 @@ static CURLcode multi_do(struct Curl_easy *data, bool *done) * DOING state there's more work to do! */ -static CURLcode multi_do_more(struct connectdata *conn, int *complete) +static CURLcode multi_do_more(struct Curl_easy *data, int *complete) { CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; *complete = 0; if(conn->handler->do_more) - result = conn->handler->do_more(conn, complete); - - if(!result && (*complete == 1)) - /* do_complete must be called after the protocol-specific DO function */ - do_complete(conn); + result = conn->handler->do_more(data, complete); return result; } /* + * Check whether a timeout occurred, and handle it if it did + */ +static bool multi_handle_timeout(struct Curl_easy *data, + struct curltime *now, + bool *stream_error, + CURLcode *result, + bool connect_timeout) +{ + timediff_t timeout_ms; + timeout_ms = Curl_timeleft(data, now, connect_timeout); + + if(timeout_ms < 0) { + /* Handle timed out */ + if(data->mstate == MSTATE_RESOLVING) + failf(data, "Resolving timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds", + Curl_timediff(*now, data->progress.t_startsingle)); + else if(data->mstate == MSTATE_CONNECTING) + failf(data, "Connection timed out after %" CURL_FORMAT_TIMEDIFF_T + " milliseconds", + Curl_timediff(*now, data->progress.t_startsingle)); + 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", + Curl_timediff(*now, data->progress.t_startsingle), + 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, data->progress.t_startsingle), + k->bytecount); + } + } + + /* Force connection closed if the connection has indeed been used */ + if(data->mstate > MSTATE_DO) { + streamclose(data->conn, "Disconnected with pending data"); + *stream_error = TRUE; + } + *result = CURLE_OPERATION_TIMEDOUT; + (void)multi_done(data, *result, TRUE); + } + + return (timeout_ms < 0); +} + +/* * We are doing protocol-specific connecting and this is being called over and * over from the multi interface until the connection phase is done on * protocol layer. */ -static CURLcode protocol_connecting(struct connectdata *conn, - bool *done) +static CURLcode protocol_connecting(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; if(conn && conn->handler->connecting) { *done = FALSE; - result = conn->handler->connecting(conn, done); + result = conn->handler->connecting(data, done); } else *done = TRUE; @@ -1608,13 +1630,14 @@ static CURLcode protocol_connecting(struct connectdata *conn, * until the DOING phase is done on protocol layer. */ -static CURLcode protocol_doing(struct connectdata *conn, bool *done) +static CURLcode protocol_doing(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; if(conn && conn->handler->doing) { *done = FALSE; - result = conn->handler->doing(conn, done); + result = conn->handler->doing(data, done); } else *done = TRUE; @@ -1627,11 +1650,11 @@ static CURLcode protocol_doing(struct connectdata *conn, bool *done) * proceed with some action. * */ -static CURLcode protocol_connect(struct connectdata *conn, +static CURLcode protocol_connect(struct Curl_easy *data, bool *protocol_done) { CURLcode result = CURLE_OK; - + struct connectdata *conn = data->conn; DEBUGASSERT(conn); DEBUGASSERT(protocol_done); @@ -1652,7 +1675,7 @@ static CURLcode protocol_connect(struct connectdata *conn, if(!conn->bits.protoconnstart) { #ifndef CURL_DISABLE_PROXY - result = Curl_proxy_connect(conn, FIRSTSOCKET); + result = Curl_proxy_connect(data, FIRSTSOCKET); if(result) return result; @@ -1670,7 +1693,7 @@ static CURLcode protocol_connect(struct connectdata *conn, /* is there a protocol-specific connect() procedure? */ /* Call the protocol-specific connect function */ - result = conn->handler->connect_it(conn, protocol_done); + result = conn->handler->connect_it(data, protocol_done); } else *protocol_done = TRUE; @@ -1712,7 +1735,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, bool done = FALSE; CURLMcode rc; CURLcode result = CURLE_OK; - timediff_t timeout_ms; timediff_t recv_timeout_ms; timediff_t send_timeout_ms; int control; @@ -1727,84 +1749,55 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, rc = CURLM_OK; if(multi_ischanged(multi, TRUE)) { - DEBUGF(infof(data, "multi changed, check CONNECT_PEND queue!\n")); + DEBUGF(infof(data, "multi changed, check CONNECT_PEND queue!")); process_pending_handles(multi); /* multiplexed */ } - if(data->conn && data->mstate > CURLM_STATE_CONNECT && - data->mstate < CURLM_STATE_COMPLETED) { + if(data->mstate > MSTATE_CONNECT && + data->mstate < MSTATE_COMPLETED) { /* Make sure we set the connection's current owner */ - data->conn->data = data; + DEBUGASSERT(data->conn); + if(!data->conn) + return CURLM_INTERNAL_ERROR; } if(data->conn && - (data->mstate >= CURLM_STATE_CONNECT) && - (data->mstate < CURLM_STATE_COMPLETED)) { + (data->mstate >= MSTATE_CONNECT) && + (data->mstate < MSTATE_COMPLETED)) { + /* Check for overall operation timeout here but defer handling the + * connection timeout to later, to allow for a connection to be set up + * in the window since we last checked timeout. This prevents us + * tearing down a completed connection in the case where we were slow + * to check the timeout (e.g. process descheduled during this loop). + * We set connect_timeout=FALSE to do this. */ + /* we need to wait for the connect state as only then is the start time stored, but we must not check already completed handles */ - timeout_ms = Curl_timeleft(data, nowp, - (data->mstate <= CURLM_STATE_DO)? - TRUE:FALSE); - - if(timeout_ms < 0) { - /* Handle timed out */ - if(data->mstate == CURLM_STATE_WAITRESOLVE) - failf(data, "Resolving timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds", - Curl_timediff(*nowp, data->progress.t_startsingle)); - else if(data->mstate == CURLM_STATE_WAITCONNECT) - failf(data, "Connection timed out after %" CURL_FORMAT_TIMEDIFF_T - " milliseconds", - Curl_timediff(*nowp, data->progress.t_startsingle)); - 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", - Curl_timediff(*nowp, data->progress.t_startsingle), - 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(*nowp, data->progress.t_startsingle), - k->bytecount); - } - } - - /* Force connection closed if the connection has indeed been used */ - if(data->mstate > CURLM_STATE_DO) { - streamclose(data->conn, "Disconnected with pending data"); - stream_error = TRUE; - } - result = CURLE_OPERATION_TIMEDOUT; - (void)multi_done(data, result, TRUE); + if(multi_handle_timeout(data, nowp, &stream_error, &result, FALSE)) { /* Skip the statemachine and go directly to error handling section. */ goto statemachine_end; } } switch(data->mstate) { - case CURLM_STATE_INIT: + case MSTATE_INIT: /* init this transfer. */ result = Curl_pretransfer(data); if(!result) { /* after init, go CONNECT */ - multistate(data, CURLM_STATE_CONNECT); + multistate(data, MSTATE_CONNECT); *nowp = Curl_pgrsTime(data, TIMER_STARTOP); rc = CURLM_CALL_MULTI_PERFORM; } break; - case CURLM_STATE_CONNECT_PEND: + case MSTATE_PENDING: /* We will stay here until there is a connection available. Then - we try again in the CURLM_STATE_CONNECT state. */ + we try again in the MSTATE_CONNECT state. */ break; - case CURLM_STATE_CONNECT: + case MSTATE_CONNECT: /* Connect. We want to get a connection identifier filled in. */ /* init this transfer. */ result = Curl_preconnect(data); @@ -1822,7 +1815,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(CURLE_NO_CONNECTION_AVAILABLE == result) { /* There was no connection available. We will go to the pending state and wait for an available connection. */ - multistate(data, CURLM_STATE_CONNECT_PEND); + multistate(data, MSTATE_PENDING); /* add this handle to the list of connect-pending handles */ Curl_llist_insert_next(&multi->pending, multi->pending.tail, data, @@ -1832,14 +1825,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else if(data->state.previouslypending) { /* this transfer comes from the pending queue so try move another */ - infof(data, "Transfer was pending, now try another\n"); + infof(data, "Transfer was pending, now try another"); process_pending_handles(data->multi); } if(!result) { if(async) /* We're now waiting for an asynchronous name lookup */ - multistate(data, CURLM_STATE_WAITRESOLVE); + multistate(data, MSTATE_RESOLVING); else { /* after the connect has been sent off, go WAITCONNECT unless the protocol connect is already done and we can go directly to @@ -1847,20 +1840,20 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, rc = CURLM_CALL_MULTI_PERFORM; if(protocol_connected) - multistate(data, CURLM_STATE_DO); + multistate(data, MSTATE_DO); else { #ifndef CURL_DISABLE_HTTP if(Curl_connect_ongoing(data->conn)) - multistate(data, CURLM_STATE_WAITPROXYCONNECT); + multistate(data, MSTATE_TUNNELING); else #endif - multistate(data, CURLM_STATE_WAITCONNECT); + multistate(data, MSTATE_CONNECTING); } } } break; - case CURLM_STATE_WAITRESOLVE: + case MSTATE_RESOLVING: /* awaiting an asynch name resolve to complete */ { struct Curl_dns_entry *dns = NULL; @@ -1879,19 +1872,19 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, hostname = conn->host.name; /* check if we have the name resolved by now */ - dns = Curl_fetch_addr(conn, hostname, (int)conn->port); + dns = Curl_fetch_addr(data, hostname, (int)conn->port); if(dns) { #ifdef CURLRES_ASYNCH - conn->async.dns = dns; - conn->async.done = TRUE; + data->state.async.dns = dns; + data->state.async.done = TRUE; #endif result = CURLE_OK; - infof(data, "Hostname '%s' was found in DNS cache\n", hostname); + infof(data, "Hostname '%s' was found in DNS cache", hostname); } if(!dns) - result = Curl_resolv_check(data->conn, &dns); + result = Curl_resolv_check(data, &dns); /* Update sockets here, because the socket(s) may have been closed and the application thus needs to be told, even if it @@ -1904,7 +1897,7 @@ 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->conn, &protocol_connected); + result = Curl_once_resolved(data, &protocol_connected); if(result) /* if Curl_once_resolved() returns failure, the connection struct @@ -1914,14 +1907,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* call again please so that we get the next socket setup */ rc = CURLM_CALL_MULTI_PERFORM; if(protocol_connected) - multistate(data, CURLM_STATE_DO); + multistate(data, MSTATE_DO); else { #ifndef CURL_DISABLE_HTTP if(Curl_connect_ongoing(data->conn)) - multistate(data, CURLM_STATE_WAITPROXYCONNECT); + multistate(data, MSTATE_TUNNELING); else #endif - multistate(data, CURLM_STATE_WAITCONNECT); + multistate(data, MSTATE_CONNECTING); } } } @@ -1935,17 +1928,17 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; #ifndef CURL_DISABLE_HTTP - case CURLM_STATE_WAITPROXYCONNECT: + case MSTATE_TUNNELING: /* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */ DEBUGASSERT(data->conn); - result = Curl_http_connect(data->conn, &protocol_connected); + result = Curl_http_connect(data, &protocol_connected); #ifndef CURL_DISABLE_PROXY if(data->conn->bits.proxy_connect_closed) { rc = CURLM_CALL_MULTI_PERFORM; /* connect back to proxy again */ result = CURLE_OK; multi_done(data, CURLE_OK, FALSE); - multistate(data, CURLM_STATE_CONNECT); + multistate(data, MSTATE_CONNECT); } else #endif @@ -1958,7 +1951,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, Curl_connect_complete(data->conn)) { rc = CURLM_CALL_MULTI_PERFORM; /* initiate protocol connect phase */ - multistate(data, CURLM_STATE_SENDPROTOCONNECT); + multistate(data, MSTATE_PROTOCONNECT); } } else @@ -1966,10 +1959,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; #endif - case CURLM_STATE_WAITCONNECT: + case MSTATE_CONNECTING: /* awaiting a completion of an asynch TCP connect */ DEBUGASSERT(data->conn); - result = Curl_is_connected(data->conn, FIRSTSOCKET, &connected); + result = Curl_is_connected(data, data->conn, FIRSTSOCKET, &connected); if(connected && !result) { #ifndef CURL_DISABLE_HTTP if( @@ -1978,7 +1971,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, !data->conn->bits.proxy_ssl_connected[FIRSTSOCKET]) || #endif Curl_connect_ongoing(data->conn)) { - multistate(data, CURLM_STATE_WAITPROXYCONNECT); + multistate(data, MSTATE_TUNNELING); break; } #endif @@ -1986,10 +1979,9 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, #ifndef CURL_DISABLE_PROXY multistate(data, data->conn->bits.tunnel_proxy? - CURLM_STATE_WAITPROXYCONNECT: - CURLM_STATE_SENDPROTOCONNECT); + MSTATE_TUNNELING : MSTATE_PROTOCONNECT); #else - multistate(data, CURLM_STATE_SENDPROTOCONNECT); + multistate(data, MSTATE_PROTOCONNECT); #endif } else if(result) { @@ -2001,14 +1993,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } break; - case CURLM_STATE_SENDPROTOCONNECT: - result = protocol_connect(data->conn, &protocol_connected); + case MSTATE_PROTOCONNECT: + result = protocol_connect(data, &protocol_connected); if(!result && !protocol_connected) /* switch to waiting state */ - multistate(data, CURLM_STATE_PROTOCONNECT); + multistate(data, MSTATE_PROTOCONNECTING); else if(!result) { /* protocol connect has completed, go WAITDO or DO */ - multistate(data, CURLM_STATE_DO); + multistate(data, MSTATE_DO); rc = CURLM_CALL_MULTI_PERFORM; } else { @@ -2019,12 +2011,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } break; - case CURLM_STATE_PROTOCONNECT: + case MSTATE_PROTOCONNECTING: /* protocol-specific connect phase */ - result = protocol_connecting(data->conn, &protocol_connected); + result = protocol_connecting(data, &protocol_connected); if(!result && protocol_connected) { /* after the connect has completed, go WAITDO or DO */ - multistate(data, CURLM_STATE_DO); + multistate(data, MSTATE_DO); rc = CURLM_CALL_MULTI_PERFORM; } else if(result) { @@ -2035,11 +2027,33 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } break; - case CURLM_STATE_DO: + case MSTATE_DO: + if(data->set.fprereq) { + int prereq_rc; + + /* call the prerequest callback function */ + Curl_set_in_callback(data, true); + prereq_rc = data->set.fprereq(data->set.prereq_userp, + data->info.conn_primary_ip, + data->info.conn_local_ip, + data->info.conn_primary_port, + data->info.conn_local_port); + Curl_set_in_callback(data, false); + if(prereq_rc != CURL_PREREQFUNC_OK) { + failf(data, "operation aborted by pre-request callback"); + /* failure in pre-request callback - don't do any other processing */ + result = CURLE_ABORTED_BY_CALLBACK; + Curl_posttransfer(data); + multi_done(data, result, FALSE); + stream_error = TRUE; + break; + } + } + if(data->set.connect_only) { /* keep connection open for application to use the socket */ connkeep(data->conn, "CONNECT_ONLY"); - multistate(data, CURLM_STATE_DONE); + multistate(data, MSTATE_DONE); result = CURLE_OK; rc = CURLM_CALL_MULTI_PERFORM; } @@ -2058,7 +2072,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) { /* skip some states if it is important */ multi_done(data, CURLE_OK, FALSE); - multistate(data, CURLM_STATE_DONE); + + /* if there's no connection left, skip the DONE state */ + multistate(data, data->conn ? + MSTATE_DONE : MSTATE_COMPLETED); rc = CURLM_CALL_MULTI_PERFORM; break; } @@ -2066,7 +2083,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, #endif /* DO was not completed in one function call, we must continue DOING... */ - multistate(data, CURLM_STATE_DOING); + multistate(data, MSTATE_DOING); rc = CURLM_OK; } @@ -2074,12 +2091,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, 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); + multistate(data, MSTATE_DOING_MORE); rc = CURLM_OK; } else { - /* we're done with the DO, now DO_DONE */ - multistate(data, CURLM_STATE_DO_DONE); + /* we're done with the DO, now DID */ + multistate(data, MSTATE_DID); rc = CURLM_CALL_MULTI_PERFORM; } } @@ -2094,7 +2111,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, followtype follow = FOLLOW_NONE; CURLcode drc; - drc = Curl_retry_request(data->conn, &newurl); + drc = Curl_retry_request(data, &newurl); if(drc) { /* a failure here pretty much implies an out of memory */ result = drc; @@ -2111,7 +2128,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, follow = FOLLOW_RETRY; drc = Curl_follow(data, newurl, follow); if(!drc) { - multistate(data, CURLM_STATE_CONNECT); + multistate(data, MSTATE_CONNECT); rc = CURLM_CALL_MULTI_PERFORM; result = CURLE_OK; } @@ -2141,16 +2158,15 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } break; - case CURLM_STATE_DOING: + case MSTATE_DOING: /* we continue DOING until the DO phase is complete */ DEBUGASSERT(data->conn); - result = protocol_doing(data->conn, &dophase_done); + result = protocol_doing(data, &dophase_done); if(!result) { if(dophase_done) { /* after DO, go DO_DONE or DO_MORE */ multistate(data, data->conn->bits.do_more? - CURLM_STATE_DO_MORE: - CURLM_STATE_DO_DONE); + MSTATE_DOING_MORE : MSTATE_DID); rc = CURLM_CALL_MULTI_PERFORM; } /* dophase_done */ } @@ -2162,20 +2178,19 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } break; - case CURLM_STATE_DO_MORE: + case MSTATE_DOING_MORE: /* - * When we are connected, DO MORE and then go DO_DONE + * When we are connected, DOING MORE and then go DID */ DEBUGASSERT(data->conn); - result = multi_do_more(data->conn, &control); + result = multi_do_more(data, &control); if(!result) { if(control) { /* if positive, advance to DO_DONE if negative, go back to DOING */ multistate(data, control == 1? - CURLM_STATE_DO_DONE: - CURLM_STATE_DOING); + MSTATE_DID : MSTATE_DOING); rc = CURLM_CALL_MULTI_PERFORM; } else @@ -2190,7 +2205,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } break; - case CURLM_STATE_DO_DONE: + case MSTATE_DID: DEBUGASSERT(data->conn); if(data->conn->bits.multiplex) /* Check if we can move pending requests to send pipe */ @@ -2200,7 +2215,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, Having both BAD is a signal to skip immediately to DONE */ if((data->conn->sockfd != CURL_SOCKET_BAD) || (data->conn->writesockfd != CURL_SOCKET_BAD)) - multistate(data, CURLM_STATE_PERFORM); + multistate(data, MSTATE_PERFORMING); else { #ifndef CURL_DISABLE_FTP if(data->state.wildcardmatch && @@ -2208,22 +2223,30 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, data->wildcard.state = CURLWC_DONE; } #endif - multistate(data, CURLM_STATE_DONE); + multistate(data, MSTATE_DONE); } rc = CURLM_CALL_MULTI_PERFORM; break; - case CURLM_STATE_TOOFAST: /* limit-rate exceeded in either direction */ + case MSTATE_RATELIMITING: /* limit-rate exceeded in either direction */ DEBUGASSERT(data->conn); /* if both rates are within spec, resume transfer */ - if(Curl_pgrsUpdate(data->conn)) + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else result = Curl_speedcheck(data, *nowp); - if(!result) { + if(result) { + if(!(data->conn->handler->flags & PROTOPT_DUAL) && + result != CURLE_HTTP2_STREAM) + streamclose(data->conn, "Transfer returned error"); + + Curl_posttransfer(data); + multi_done(data, result, TRUE); + } + else { send_timeout_ms = 0; - if(data->set.max_send_speed > 0) + if(data->set.max_send_speed) send_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.uploaded, data->progress.ul_limit_size, @@ -2232,7 +2255,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, *nowp); recv_timeout_ms = 0; - if(data->set.max_recv_speed > 0) + if(data->set.max_recv_speed) recv_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.downloaded, data->progress.dl_limit_size, @@ -2241,7 +2264,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, *nowp); if(!send_timeout_ms && !recv_timeout_ms) { - multistate(data, CURLM_STATE_PERFORM); + multistate(data, MSTATE_PERFORMING); Curl_ratelimit(data, *nowp); } else if(send_timeout_ms >= recv_timeout_ms) @@ -2251,7 +2274,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } break; - case CURLM_STATE_PERFORM: + case MSTATE_PERFORMING: { char *newurl = NULL; bool retry = FALSE; @@ -2259,7 +2282,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, DEBUGASSERT(data->state.buffer); /* check if over send speed */ send_timeout_ms = 0; - if(data->set.max_send_speed > 0) + if(data->set.max_send_speed) send_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.uploaded, data->progress.ul_limit_size, data->set.max_send_speed, @@ -2268,7 +2291,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* check if over recv speed */ recv_timeout_ms = 0; - if(data->set.max_recv_speed > 0) + if(data->set.max_recv_speed) recv_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.downloaded, data->progress.dl_limit_size, data->set.max_recv_speed, @@ -2277,7 +2300,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(send_timeout_ms || recv_timeout_ms) { Curl_ratelimit(data, *nowp); - multistate(data, CURLM_STATE_TOOFAST); + multistate(data, MSTATE_RATELIMITING); if(send_timeout_ms >= recv_timeout_ms) Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); else @@ -2293,7 +2316,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, * 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->conn, &newurl); + CURLcode ret = Curl_retry_request(data, &newurl); if(!ret) retry = (newurl)?TRUE:FALSE; else if(!result) @@ -2307,17 +2330,18 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } } else if((CURLE_HTTP2_STREAM == result) && - Curl_h2_http_1_1_error(data->conn)) { - CURLcode ret = Curl_retry_request(data->conn, &newurl); + Curl_h2_http_1_1_error(data)) { + CURLcode ret = Curl_retry_request(data, &newurl); if(!ret) { - infof(data, "Downgrades to HTTP/1.1!\n"); - data->set.httpversion = CURL_HTTP_VERSION_1_1; + infof(data, "Downgrades to HTTP/1.1!"); + streamclose(data->conn, "Disconnect HTTP/2 for HTTP/1"); + data->state.httpwant = CURL_HTTP_VERSION_1_1; /* clear the error message bit too as we ignore the one we got */ data->state.errorbuf = FALSE; if(!newurl) /* typically for HTTP_1_1_REQUIRED error on first flight */ - newurl = strdup(data->change.url); + newurl = strdup(data->state.url); /* if we are to retry, set the result to OK and consider the request as done */ retry = TRUE; @@ -2345,7 +2369,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, multi_done(data, result, TRUE); } else if(done) { - followtype follow = FOLLOW_NONE; /* call this even if the readwrite function returned error */ Curl_posttransfer(data); @@ -2353,6 +2376,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* When we follow redirects or is set to retry the connection, we must to go back to the CONNECT state */ if(data->req.newurl || retry) { + followtype follow = FOLLOW_NONE; if(!retry) { /* if the URL is a follow-location and not just a retried request then figure out the URL here */ @@ -2367,7 +2391,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* multi_done() might return CURLE_GOT_NOTHING */ result = Curl_follow(data, newurl, follow); if(!result) { - multistate(data, CURLM_STATE_CONNECT); + multistate(data, MSTATE_CONNECT); rc = CURLM_CALL_MULTI_PERFORM; } free(newurl); @@ -2390,7 +2414,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } if(!result) { - multistate(data, CURLM_STATE_DONE); + multistate(data, MSTATE_DONE); rc = CURLM_CALL_MULTI_PERFORM; } } @@ -2405,7 +2429,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; } - case CURLM_STATE_DONE: + case MSTATE_DONE: /* this state is highly transient, so run another loop after this */ rc = CURLM_CALL_MULTI_PERFORM; @@ -2422,44 +2446,51 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, /* allow a previously set error code take precedence */ if(!result) result = res; - - /* - * If there are other handles on the connection, multi_done won't set - * 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 - */ - Curl_detach_connnection(data); } #ifndef CURL_DISABLE_FTP if(data->state.wildcardmatch) { if(data->wildcard.state != CURLWC_DONE) { /* if a wildcard is set and we are not ending -> lets start again - with CURLM_STATE_INIT */ - multistate(data, CURLM_STATE_INIT); + with MSTATE_INIT */ + multistate(data, MSTATE_INIT); break; } } #endif /* after we have DONE what we're supposed to do, go COMPLETED, and it doesn't matter what the multi_done() returned! */ - multistate(data, CURLM_STATE_COMPLETED); + multistate(data, MSTATE_COMPLETED); break; - case CURLM_STATE_COMPLETED: + case MSTATE_COMPLETED: break; - case CURLM_STATE_MSGSENT: + case MSTATE_MSGSENT: data->result = result; return CURLM_OK; /* do nothing */ default: return CURLM_INTERNAL_ERROR; } + + if(data->conn && + data->mstate >= MSTATE_CONNECT && + data->mstate < MSTATE_DO && + rc != CURLM_CALL_MULTI_PERFORM && + !multi_ischanged(multi, false)) { + /* We now handle stream timeouts if and only if this will be the last + * loop iteration. We only check this on the last iteration to ensure + * that if we know we have additional work to do immediately + * (i.e. CURLM_CALL_MULTI_PERFORM == TRUE) then we should do that before + * declaring the connection timed out as we may almost have a completed + * connection. */ + multi_handle_timeout(data, nowp, &stream_error, &result, TRUE); + } + statemachine_end: - if(data->mstate < CURLM_STATE_COMPLETED) { + if(data->mstate < MSTATE_COMPLETED) { if(result) { /* * If an error was returned, and we aren't in completed state now, @@ -2490,29 +2521,29 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, Curl_disconnect(data, conn, dead_connection); } } - else if(data->mstate == CURLM_STATE_CONNECT) { + else if(data->mstate == MSTATE_CONNECT) { /* Curl_connect() failed */ (void)Curl_posttransfer(data); } - multistate(data, CURLM_STATE_COMPLETED); + multistate(data, MSTATE_COMPLETED); rc = CURLM_CALL_MULTI_PERFORM; } /* if there's still a connection to use, call the progress function */ - else if(data->conn && Curl_pgrsUpdate(data->conn)) { + else if(data->conn && Curl_pgrsUpdate(data)) { /* aborted due to progress callback return code must close the connection */ result = CURLE_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)? - CURLM_STATE_DONE: CURLM_STATE_COMPLETED); + multistate(data, (data->mstate < MSTATE_DONE)? + MSTATE_DONE: MSTATE_COMPLETED); rc = CURLM_CALL_MULTI_PERFORM; } } - if(CURLM_STATE_COMPLETED == data->mstate) { + if(MSTATE_COMPLETED == data->mstate) { if(data->set.fmultidone) { /* signal via callback instead */ data->set.fmultidone(data, result); @@ -2528,7 +2559,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, rc = multi_addmsg(multi, msg); DEBUGASSERT(!data->conn); } - multistate(data, CURLM_STATE_MSGSENT); + multistate(data, MSTATE_MSGSENT); } } while((rc == CURLM_CALL_MULTI_PERFORM) || multi_ischanged(multi, FALSE)); @@ -2600,9 +2631,9 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; - multi->type = 0; /* not good anymore */ + multi->magic = 0; /* not good anymore */ - /* Firsrt remove all remaining easy handles */ + /* First remove all remaining easy handles */ data = multi->easyp; while(data) { nextdata = data->next; @@ -2705,7 +2736,7 @@ static CURLMcode singlesocket(struct Curl_multi *multi, curl_socket_t s; int num; unsigned int curraction; - int actions[MAX_SOCKSPEREASYHANDLE]; + unsigned char actions[MAX_SOCKSPEREASYHANDLE]; for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) socks[i] = CURL_SOCKET_BAD; @@ -2722,9 +2753,9 @@ static CURLMcode singlesocket(struct Curl_multi *multi, for(i = 0; (i< MAX_SOCKSPEREASYHANDLE) && (curraction & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i))); i++) { - unsigned int action = CURL_POLL_NONE; - unsigned int prevaction = 0; - unsigned int comboaction; + unsigned char action = CURL_POLL_NONE; + unsigned char prevaction = 0; + int comboaction; bool sincebefore = FALSE; s = socks[i]; @@ -2782,10 +2813,10 @@ static CURLMcode singlesocket(struct Curl_multi *multi, } comboaction = (entry->writers? CURL_POLL_OUT : 0) | - (entry->readers ? CURL_POLL_IN : 0); + (entry->readers ? CURL_POLL_IN : 0); /* socket existed before and has the same action set as before */ - if(sincebefore && (entry->action == comboaction)) + if(sincebefore && ((int)entry->action == comboaction)) /* same, continue */ continue; @@ -2818,7 +2849,7 @@ static CURLMcode singlesocket(struct Curl_multi *multi, /* if this is NULL here, the socket has been closed and notified so already by Curl_multi_closed() */ if(entry) { - int oldactions = data->actions[i]; + unsigned char oldactions = data->actions[i]; /* this socket has been removed. Decrease user count */ entry->users--; if(oldactions & CURL_POLL_OUT) @@ -2843,7 +2874,7 @@ static CURLMcode singlesocket(struct Curl_multi *multi, } /* for loop over numsocks */ memcpy(data->sockets, socks, num*sizeof(curl_socket_t)); - memcpy(data->actions, actions, num*sizeof(int)); + memcpy(data->actions, actions, num*sizeof(char)); data->numsocks = num; return CURLM_OK; } @@ -3158,7 +3189,6 @@ CURLMcode curl_multi_socket_action(struct Curl_multi *multi, curl_socket_t s, } CURLMcode curl_multi_socket_all(struct Curl_multi *multi, int *running_handles) - { CURLMcode result; if(multi->in_callback) @@ -3172,7 +3202,7 @@ CURLMcode curl_multi_socket_all(struct Curl_multi *multi, int *running_handles) static CURLMcode multi_timeout(struct Curl_multi *multi, long *timeout_ms) { - static struct curltime tv_zero = {0, 0}; + static const struct curltime tv_zero = {0, 0}; if(multi->timetree) { /* we have a tree of expire times */ @@ -3376,11 +3406,10 @@ void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) /* Since this is an updated time, we must remove the previous entry from the splay tree first and then re-add the new value */ - rc = Curl_splayremovebyaddr(multi->timetree, - &data->state.timenode, - &multi->timetree); + rc = Curl_splayremove(multi->timetree, &data->state.timenode, + &multi->timetree); if(rc) - infof(data, "Internal error removing splay node = %d\n", rc); + infof(data, "Internal error removing splay node = %d", rc); } /* Indicate that we are in the splay tree and insert the new timer expiry @@ -3424,11 +3453,10 @@ void Curl_expire_clear(struct Curl_easy *data) struct Curl_llist *list = &data->state.timeoutlist; int rc; - rc = Curl_splayremovebyaddr(multi->timetree, - &data->state.timenode, - &multi->timetree); + rc = Curl_splayremove(multi->timetree, &data->state.timenode, + &multi->timetree); if(rc) - infof(data, "Internal error clearing splay node = %d\n", rc); + infof(data, "Internal error clearing splay node = %d", rc); /* flush the timeout list too */ while(list->size > 0) { @@ -3436,7 +3464,7 @@ void Curl_expire_clear(struct Curl_easy *data) } #ifdef DEBUGBUILD - infof(data, "Expire cleared (transfer %p)\n", data); + infof(data, "Expire cleared (transfer %p)", data); #endif nowp->tv_sec = 0; nowp->tv_usec = 0; @@ -3478,16 +3506,18 @@ size_t Curl_multi_max_total_connections(struct Curl_multi *multi) * When information about a connection has appeared, call this! */ -void Curl_multiuse_state(struct connectdata *conn, +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); - DEBUGASSERT(conn->data); - DEBUGASSERT(conn->data->multi); conn->bundle->multiuse = bundlestate; - process_pending_handles(conn->data->multi); + process_pending_handles(data->multi); } static void process_pending_handles(struct Curl_multi *multi) @@ -3496,9 +3526,9 @@ static void process_pending_handles(struct Curl_multi *multi) if(e) { struct Curl_easy *data = e->ptr; - DEBUGASSERT(data->mstate == CURLM_STATE_CONNECT_PEND); + DEBUGASSERT(data->mstate == MSTATE_PENDING); - multistate(data, CURLM_STATE_CONNECT); + multistate(data, MSTATE_CONNECT); /* Remove this node from the list */ Curl_llist_remove(&multi->pending, e, NULL); @@ -3536,7 +3566,7 @@ void Curl_multi_dump(struct Curl_multi *multi) fprintf(stderr, "* Multi status: %d handles, %d alive\n", multi->num_easy, multi->num_alive); for(data = multi->easyp; data; data = data->next) { - if(data->mstate < CURLM_STATE_COMPLETED) { + if(data->mstate < MSTATE_COMPLETED) { /* only display handles that are not completed */ fprintf(stderr, "handle %p, state %s, %d sockets\n", (void *)data, |