diff options
Diffstat (limited to 'libs/libcurl/src/multi.c')
-rw-r--r-- | libs/libcurl/src/multi.c | 226 |
1 files changed, 154 insertions, 72 deletions
diff --git a/libs/libcurl/src/multi.c b/libs/libcurl/src/multi.c index f307d63b93..f8dcc63b47 100644 --- a/libs/libcurl/src/multi.c +++ b/libs/libcurl/src/multi.c @@ -243,6 +243,26 @@ static void trhash_dtor(void *nada) (void)nada; } +/* + * The sockhash has its own separate subhash in each entry that need to be + * safely destroyed first. + */ +static void sockhash_destroy(struct Curl_hash *h) +{ + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + + DEBUGASSERT(h); + Curl_hash_start_iterate(h, &iter); + he = Curl_hash_next_element(&iter); + while(he) { + struct Curl_sh_entry *sh = (struct Curl_sh_entry *)he->ptr; + Curl_hash_destroy(&sh->transfers); + he = Curl_hash_next_element(&iter); + } + Curl_hash_destroy(h); +} + /* make sure this socket is present in the hash for this handle */ static struct Curl_sh_entry *sh_addentry(struct Curl_hash *sh, @@ -261,11 +281,8 @@ static struct Curl_sh_entry *sh_addentry(struct Curl_hash *sh, if(!check) return NULL; /* major failure */ - if(Curl_hash_init(&check->transfers, TRHASH_SIZE, trhash, - trhash_compare, trhash_dtor)) { - free(check); - return NULL; - } + Curl_hash_init(&check->transfers, TRHASH_SIZE, trhash, trhash_compare, + trhash_dtor); /* make/add new hash entry */ if(!Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) { @@ -332,10 +349,10 @@ static size_t hash_fd(void *key, size_t key_length, size_t slots_num) * per call." * */ -static int sh_init(struct Curl_hash *hash, int hashsize) +static void sh_init(struct Curl_hash *hash, int hashsize) { - return Curl_hash_init(hash, hashsize, hash_fd, fd_key_compare, - sh_freeentry); + Curl_hash_init(hash, hashsize, hash_fd, fd_key_compare, + sh_freeentry); } /* @@ -362,11 +379,9 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ multi->magic = CURL_MULTI_HANDLE; - if(Curl_mk_dnscache(&multi->hostcache)) - goto error; + Curl_init_dnscache(&multi->hostcache); - if(sh_init(&multi->sockhash, hashsize)) - goto error; + sh_init(&multi->sockhash, hashsize); if(Curl_conncache_init(&multi->conn_cache, chashsize)) goto error; @@ -405,7 +420,7 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ error: - Curl_hash_destroy(&multi->sockhash); + sockhash_destroy(&multi->sockhash); Curl_hash_destroy(&multi->hostcache); Curl_conncache_destroy(&multi->conn_cache); Curl_llist_destroy(&multi->msglist, NULL); @@ -424,6 +439,7 @@ struct Curl_multi *curl_multi_init(void) CURLMcode curl_multi_add_handle(struct Curl_multi *multi, struct Curl_easy *data) { + CURLMcode rc; /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -440,6 +456,15 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; + if(multi->dead) { + /* a "dead" handle cannot get added transfers while any existing easy + handles are still alive - but if there are none alive anymore, it is + fine to start over and unmark the "deadness" of this handle */ + if(multi->num_alive) + return CURLM_ABORTED_BY_CALLBACK; + multi->dead = FALSE; + } + /* Initialize timeout list for this handle */ Curl_llist_init(&data->state.timeoutlist, NULL); @@ -452,6 +477,34 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, if(data->set.errorbuffer) data->set.errorbuffer[0] = 0; + /* make the Curl_easy refer back to this multi handle - before Curl_expire() + is called. */ + data->multi = multi; + + /* Set the timeout for this handle to expire really soon so that it will + be taken care of even when this handle is added in the midst of operation + when only the curl_multi_socket() API is used. During that flow, only + sockets that time-out or have actions will be dealt with. Since this + handle has no action yet, we make sure it times out to get things to + happen. */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + + /* A somewhat crude work-around for a little glitch in Curl_update_timer() + that happens if the lastcall time is set to the same time when the handle + is removed as when the next handle is added, as then the check in + Curl_update_timer() that prevents calling the application multiple times + with the same timer info will not trigger and then the new handle's + timeout will not be notified to the app. + + The work-around is thus simply to clear the 'lastcall' variable to force + Curl_update_timer() to always trigger a callback to the app when a new + easy handle is added */ + memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); + + rc = Curl_update_timer(multi); + if(rc) + return rc; + /* set the easy handle */ multistate(data, MSTATE_INIT); @@ -492,35 +545,12 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, multi->easylp = multi->easyp = data; /* both first and last */ } - /* make the Curl_easy refer back to this multi handle */ - data->multi = multi; - - /* Set the timeout for this handle to expire really soon so that it will - be taken care of even when this handle is added in the midst of operation - when only the curl_multi_socket() API is used. During that flow, only - sockets that time-out or have actions will be dealt with. Since this - handle has no action yet, we make sure it times out to get things to - happen. */ - Curl_expire(data, 0, EXPIRE_RUN_NOW); - /* increase the node-counter */ multi->num_easy++; /* increase the alive-counter */ multi->num_alive++; - /* A somewhat crude work-around for a little glitch in Curl_update_timer() - that happens if the lastcall time is set to the same time when the handle - is removed as when the next handle is added, as then the check in - Curl_update_timer() that prevents calling the application multiple times - with the same timer info will not trigger and then the new handle's - timeout will not be notified to the app. - - The work-around is thus simply to clear the 'lastcall' variable to force - Curl_update_timer() to always trigger a callback to the app when a new - easy handle is added */ - memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); - CONNCACHE_LOCK(data); /* The closure handle only ever has default timeouts set. To improve the state somewhat we clone the timeouts from each added handle so that the @@ -533,14 +563,13 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, data->set.no_signal; CONNCACHE_UNLOCK(data); - Curl_update_timer(multi); return CURLM_OK; } #if 0 /* Debug-function, used like this: * - * Curl_hash_print(multi->sockhash, debug_print_sock_hash); + * Curl_hash_print(&multi->sockhash, debug_print_sock_hash); * * Enable the hash print function first by editing hash.c */ @@ -548,8 +577,8 @@ static void debug_print_sock_hash(void *p) { struct Curl_sh_entry *sh = (struct Curl_sh_entry *)p; - fprintf(stderr, " [easy %p/magic %x/socket %d]", - (void *)sh->data, sh->data->magic, (int)sh->socket); + fprintf(stderr, " [readers %u][writers %u]", + sh->readers, sh->writers); } #endif @@ -562,7 +591,8 @@ static CURLcode multi_done(struct Curl_easy *data, struct connectdata *conn = data->conn; unsigned int i; - DEBUGF(infof(data, "multi_done")); + DEBUGF(infof(data, "multi_done: status: %d prem: %d done: %d", + (int)status, (int)premature, data->state.done)); if(data->state.done) /* Stop if multi_done() has already been called */ @@ -719,6 +749,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, struct Curl_easy *easy = data; bool premature; struct Curl_llist_element *e; + CURLMcode rc; /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) @@ -792,8 +823,11 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, /* change state without using multistate(), only to make singlesocket() do what we want */ data->mstate = MSTATE_COMPLETED; - singlesocket(multi, easy); /* to let the application know what sockets that - vanish with this handle */ + + /* This ignores the return code even in case of problems because there's + nothing more to do about that, here */ + (void)singlesocket(multi, easy); /* to let the application know what sockets + that vanish with this handle */ /* Remove the association between the connection and the handle */ Curl_detach_connnection(data); @@ -858,7 +892,9 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, process_pending_handles(multi); - Curl_update_timer(multi); + rc = Curl_update_timer(multi); + if(rc) + return rc; return CURLM_OK; } @@ -878,6 +914,7 @@ void Curl_detach_connnection(struct Curl_easy *data) { struct connectdata *conn = data->conn; if(conn) { + Curl_connect_done(data); /* if mid-CONNECT, shut it down */ Curl_llist_remove(&conn->easyq, &data->conn_queue, NULL); Curl_ssl_detach_conn(data, conn); } @@ -1742,6 +1779,15 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(!GOOD_EASY_HANDLE(data)) return CURLM_BAD_EASY_HANDLE; + if(multi->dead) { + /* a multi-level callback returned error before, meaning every individual + transfer now has failed */ + result = CURLE_ABORTED_BY_CALLBACK; + Curl_posttransfer(data); + multi_done(data, result, FALSE); + multistate(data, MSTATE_COMPLETED); + } + do { /* A "stream" here is a logical stream if the protocol can handle that (HTTP/2), or the full connection for older protocols */ @@ -1892,7 +1938,9 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, down. If the name has not yet been resolved, it is likely that new sockets have been opened in an attempt to contact another resolver. */ - singlesocket(multi, data); + rc = singlesocket(multi, data); + if(rc) + return rc; if(dns) { /* Perform the next step in the connection phase, and then move on @@ -2617,7 +2665,7 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) *running_handles = multi->num_alive; if(CURLM_OK >= returncode) - Curl_update_timer(multi); + returncode = Curl_update_timer(multi); return returncode; } @@ -2662,7 +2710,7 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) /* Close all the connections in the connection cache */ Curl_conncache_close_all_connections(&multi->conn_cache); - Curl_hash_destroy(&multi->sockhash); + sockhash_destroy(&multi->sockhash); Curl_conncache_destroy(&multi->conn_cache); Curl_llist_destroy(&multi->msglist, NULL); Curl_llist_destroy(&multi->pending, NULL); @@ -2737,6 +2785,7 @@ static CURLMcode singlesocket(struct Curl_multi *multi, int num; unsigned int curraction; unsigned char actions[MAX_SOCKSPEREASYHANDLE]; + int rc; for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) socks[i] = CURL_SOCKET_BAD; @@ -2808,8 +2857,10 @@ static CURLMcode singlesocket(struct Curl_multi *multi, /* add 'data' to the transfer hash on this socket! */ if(!Curl_hash_add(&entry->transfers, (char *)&data, /* hash key */ - sizeof(struct Curl_easy *), data)) + sizeof(struct Curl_easy *), data)) { + Curl_hash_destroy(&entry->transfers); return CURLM_OUT_OF_MEMORY; + } } comboaction = (entry->writers? CURL_POLL_OUT : 0) | @@ -2820,9 +2871,14 @@ static CURLMcode singlesocket(struct Curl_multi *multi, /* same, continue */ continue; - if(multi->socket_cb) - multi->socket_cb(data, s, comboaction, multi->socket_userp, - entry->socketp); + if(multi->socket_cb) { + rc = multi->socket_cb(data, s, comboaction, multi->socket_userp, + entry->socketp); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + } entry->action = comboaction; /* store the current action state */ } @@ -2857,10 +2913,14 @@ static CURLMcode singlesocket(struct Curl_multi *multi, if(oldactions & CURL_POLL_IN) entry->readers--; if(!entry->users) { - if(multi->socket_cb) - multi->socket_cb(data, s, CURL_POLL_REMOVE, - multi->socket_userp, - entry->socketp); + if(multi->socket_cb) { + rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, + multi->socket_userp, entry->socketp); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + } sh_delentry(entry, &multi->sockhash, s); } else { @@ -2879,9 +2939,11 @@ static CURLMcode singlesocket(struct Curl_multi *multi, return CURLM_OK; } -void Curl_updatesocket(struct Curl_easy *data) +CURLcode Curl_updatesocket(struct Curl_easy *data) { - singlesocket(data->multi, data); + if(singlesocket(data->multi, data)) + return CURLE_ABORTED_BY_CALLBACK; + return CURLE_OK; } @@ -2906,13 +2968,18 @@ void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s) struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s); if(entry) { + int rc = 0; if(multi->socket_cb) - multi->socket_cb(data, s, CURL_POLL_REMOVE, - multi->socket_userp, - entry->socketp); + rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, + multi->socket_userp, entry->socketp); /* now remove it from the socket hash */ sh_delentry(entry, &multi->sockhash, s); + if(rc == -1) + /* This just marks the multi handle as "dead" without returning an + error code primarily because this function is used from many + places where propagating an error back is tricky. */ + multi->dead = TRUE; } } } @@ -3172,7 +3239,7 @@ CURLMcode curl_multi_socket(struct Curl_multi *multi, curl_socket_t s, return CURLM_RECURSIVE_API_CALL; result = multi_socket(multi, FALSE, s, 0, running_handles); if(CURLM_OK >= result) - Curl_update_timer(multi); + result = Curl_update_timer(multi); return result; } @@ -3184,7 +3251,7 @@ CURLMcode curl_multi_socket_action(struct Curl_multi *multi, curl_socket_t s, return CURLM_RECURSIVE_API_CALL; result = multi_socket(multi, FALSE, s, ev_bitmask, running_handles); if(CURLM_OK >= result) - Curl_update_timer(multi); + result = Curl_update_timer(multi); return result; } @@ -3195,7 +3262,7 @@ CURLMcode curl_multi_socket_all(struct Curl_multi *multi, int *running_handles) return CURLM_RECURSIVE_API_CALL; result = multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles); if(CURLM_OK >= result) - Curl_update_timer(multi); + result = Curl_update_timer(multi); return result; } @@ -3204,6 +3271,11 @@ static CURLMcode multi_timeout(struct Curl_multi *multi, { static const struct curltime tv_zero = {0, 0}; + if(multi->dead) { + *timeout_ms = 0; + return CURLM_OK; + } + if(multi->timetree) { /* we have a tree of expire times */ struct curltime now = Curl_now(); @@ -3255,14 +3327,15 @@ CURLMcode curl_multi_timeout(struct Curl_multi *multi, * Tell the application it should update its timers, if it subscribes to the * update timer callback. */ -void Curl_update_timer(struct Curl_multi *multi) +CURLMcode Curl_update_timer(struct Curl_multi *multi) { long timeout_ms; + int rc; - if(!multi->timer_cb) - return; + if(!multi->timer_cb || multi->dead) + return CURLM_OK; if(multi_timeout(multi, &timeout_ms)) { - return; + return CURLM_OK; } if(timeout_ms < 0) { static const struct curltime none = {0, 0}; @@ -3270,10 +3343,14 @@ void Curl_update_timer(struct Curl_multi *multi) multi->timer_lastcall = none; /* there's no timeout now but there was one previously, tell the app to disable it */ - multi->timer_cb(multi, -1, multi->timer_userp); - return; + rc = multi->timer_cb(multi, -1, multi->timer_userp); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + return CURLM_OK; } - return; + return CURLM_OK; } /* When multi_timeout() is done, multi->timetree points to the node with the @@ -3281,11 +3358,16 @@ void Curl_update_timer(struct Curl_multi *multi) * if this is the same (fixed) time as we got in a previous call and then * avoid calling the callback again. */ if(Curl_splaycomparekeys(multi->timetree->key, multi->timer_lastcall) == 0) - return; + return CURLM_OK; multi->timer_lastcall = multi->timetree->key; - multi->timer_cb(multi, timeout_ms, multi->timer_userp); + rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + return CURLM_OK; } /* |