| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se> |
| * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at https://curl.se/docs/copyright.html. |
| * |
| * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
| * copies of the Software, and permit persons to whom the Software is |
| * furnished to do so, under the terms of the COPYING file. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| * SPDX-License-Identifier: curl |
| * |
| ***************************************************************************/ |
| #include "curl_setup.h" |
| |
| #include "urldata.h" |
| #include "url.h" |
| #include "cfilters.h" |
| #include "progress.h" |
| #include "multiif.h" |
| #include "multi_ev.h" |
| #include "curl_trc.h" |
| #include "cshutdn.h" |
| #include "sigpipe.h" |
| #include "connect.h" |
| #include "select.h" |
| #include "curlx/strparse.h" |
| |
| |
| static void cshutdn_run_conn_handler(struct Curl_easy *data, |
| struct connectdata *conn) |
| { |
| if(!conn->bits.shutdown_handler) { |
| |
| if(conn->scheme && conn->scheme->run->disconnect) { |
| /* Some disconnect handlers do a blocking wait on server responses. |
| * FTP/IMAP/SMTP and SFTP are among them. When using the internal |
| * handle, set an overall short timeout so we do not hang for the |
| * default 120 seconds. */ |
| if(data->state.internal) { |
| data->set.timeout = DEFAULT_SHUTDOWN_TIMEOUT_MS; |
| Curl_pgrsTime(data, TIMER_STARTOP); |
| } |
| |
| /* This is set if protocol-specific cleanups should be made */ |
| DEBUGF(infof(data, "connection #%" FMT_OFF_T |
| ", shutdown protocol handler (aborted=%d)", |
| conn->connection_id, conn->bits.aborted)); |
| /* There are protocol handlers that block on retrieving |
| * server responses here (FTP). Set a short timeout. */ |
| conn->scheme->run->disconnect(data, conn, (bool)conn->bits.aborted); |
| } |
| |
| conn->bits.shutdown_handler = TRUE; |
| } |
| } |
| |
| static void cshutdn_run_once(struct Curl_easy *data, |
| struct connectdata *conn, |
| bool *done) |
| { |
| CURLcode r1, r2; |
| bool done1, done2; |
| |
| /* We expect to be attached when called */ |
| DEBUGASSERT(data->conn == conn); |
| |
| if(!Curl_shutdown_started(data, FIRSTSOCKET)) { |
| Curl_shutdown_start(data, FIRSTSOCKET, 0); |
| } |
| |
| cshutdn_run_conn_handler(data, conn); |
| |
| if(conn->bits.shutdown_filters) { |
| *done = TRUE; |
| return; |
| } |
| |
| if(!conn->bits.connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET)) |
| r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1); |
| else { |
| r1 = CURLE_OK; |
| done1 = TRUE; |
| } |
| |
| if(!conn->bits.connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET)) |
| r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2); |
| else { |
| r2 = CURLE_OK; |
| done2 = TRUE; |
| } |
| |
| /* we are done when any failed or both report success */ |
| *done = (r1 || r2 || (done1 && done2)); |
| if(*done) |
| conn->bits.shutdown_filters = TRUE; |
| } |
| |
| void Curl_cshutdn_run_once(struct Curl_easy *data, |
| struct connectdata *conn, |
| bool *done) |
| { |
| DEBUGASSERT(!data->conn); |
| Curl_attach_connection(data, conn); |
| cshutdn_run_once(data, conn, done); |
| CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done); |
| Curl_detach_connection(data); |
| } |
| |
| void Curl_cshutdn_terminate(struct Curl_easy *data, |
| struct connectdata *conn, |
| bool do_shutdown) |
| { |
| struct Curl_easy *admin = data; |
| bool done; |
| |
| /* there must be a connection to close */ |
| DEBUGASSERT(conn); |
| /* it must be removed from the connection pool */ |
| DEBUGASSERT(!conn->bits.in_cpool); |
| /* the transfer must be detached from the connection */ |
| DEBUGASSERT(data && !data->conn); |
| |
| /* If we can obtain an internal admin handle, use that to attach |
| * and terminate the connection. Some protocol will try to mess with |
| * `data` during shutdown and we do not want that with a `data` from |
| * the application. */ |
| if(data->multi && data->multi->admin) |
| admin = data->multi->admin; |
| |
| Curl_attach_connection(admin, conn); |
| |
| cshutdn_run_conn_handler(admin, conn); |
| if(do_shutdown) { |
| /* Make a last attempt to shutdown handlers and filters, if |
| * not done so already. */ |
| cshutdn_run_once(admin, conn, &done); |
| } |
| CURL_TRC_M(admin, "[SHUTDOWN] %sclosing connection #%" FMT_OFF_T, |
| conn->bits.shutdown_filters ? "" : "force ", |
| conn->connection_id); |
| Curl_conn_close(admin, SECONDARYSOCKET); |
| Curl_conn_close(admin, FIRSTSOCKET); |
| Curl_detach_connection(admin); |
| |
| if(data->multi) |
| Curl_multi_ev_conn_done(data->multi, data, conn); |
| Curl_conn_free(admin, conn); |
| |
| if(data->multi) { |
| CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged"); |
| Curl_multi_connchanged(data->multi); |
| } |
| } |
| |
| static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, |
| struct Curl_easy *data, |
| const char *destination) |
| { |
| struct Curl_llist_node *e; |
| struct connectdata *conn; |
| |
| e = Curl_llist_head(&cshutdn->list); |
| while(e) { |
| conn = Curl_node_elem(e); |
| if(!destination || !strcmp(destination, conn->destination)) |
| break; |
| e = Curl_node_next(e); |
| } |
| |
| if(e) { |
| struct Curl_sigpipe_ctx sigpipe_ctx; |
| conn = Curl_node_elem(e); |
| Curl_node_remove(e); |
| sigpipe_init(&sigpipe_ctx); |
| sigpipe_apply(data, &sigpipe_ctx); |
| Curl_cshutdn_terminate(data, conn, FALSE); |
| sigpipe_restore(&sigpipe_ctx); |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| bool Curl_cshutdn_close_oldest(struct Curl_easy *data, |
| const char *destination) |
| { |
| if(data && data->multi) { |
| struct cshutdn *csd = &data->multi->cshutdn; |
| return cshutdn_destroy_oldest(csd, data, destination); |
| } |
| return FALSE; |
| } |
| |
| #define NUM_POLLS_ON_STACK 10 |
| |
| static CURLcode cshutdn_wait(struct cshutdn *cshutdn, |
| struct Curl_easy *data, |
| int timeout_ms) |
| { |
| struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; |
| struct curl_pollfds cpfds; |
| CURLcode result; |
| |
| Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); |
| |
| result = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds); |
| if(result) |
| goto out; |
| |
| Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000)); |
| |
| out: |
| Curl_pollfds_cleanup(&cpfds); |
| return result; |
| } |
| |
| static void cshutdn_perform(struct cshutdn *cshutdn, |
| struct Curl_easy *data, |
| struct Curl_sigpipe_ctx *sigpipe_ctx) |
| { |
| struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); |
| struct Curl_llist_node *enext; |
| struct connectdata *conn; |
| timediff_t next_expire_ms = 0, ms; |
| bool done; |
| |
| if(!e) |
| return; |
| |
| CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections", |
| Curl_llist_count(&cshutdn->list)); |
| sigpipe_apply(data, sigpipe_ctx); |
| while(e) { |
| enext = Curl_node_next(e); |
| conn = Curl_node_elem(e); |
| Curl_cshutdn_run_once(data, conn, &done); |
| if(done) { |
| Curl_node_remove(e); |
| Curl_cshutdn_terminate(data, conn, FALSE); |
| } |
| else { |
| /* idata has one timer list, but maybe more than one connection. |
| * Set EXPIRE_SHUTDOWN to the smallest time left for all. */ |
| ms = Curl_conn_shutdown_timeleft(data, conn); |
| if(ms && ms < next_expire_ms) |
| next_expire_ms = ms; |
| } |
| e = enext; |
| } |
| |
| if(next_expire_ms) |
| Curl_expire_ex(data, next_expire_ms, EXPIRE_SHUTDOWN); |
| } |
| |
| static void cshutdn_terminate_all(struct cshutdn *cshutdn, |
| struct Curl_easy *data, |
| int timeout_ms) |
| { |
| struct curltime started = *Curl_pgrs_now(data); |
| struct Curl_llist_node *e; |
| struct Curl_sigpipe_ctx sigpipe_ctx; |
| |
| DEBUGASSERT(cshutdn); |
| DEBUGASSERT(data); |
| |
| CURL_TRC_M(data, "[SHUTDOWN] shutdown all"); |
| sigpipe_init(&sigpipe_ctx); |
| |
| while(Curl_llist_head(&cshutdn->list)) { |
| timediff_t spent_ms; |
| int remain_ms; |
| |
| cshutdn_perform(cshutdn, data, &sigpipe_ctx); |
| |
| if(!Curl_llist_head(&cshutdn->list)) { |
| CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly"); |
| break; |
| } |
| |
| /* wait for activity, timeout or "nothing" */ |
| spent_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &started); |
| if(spent_ms >= (timediff_t)timeout_ms) { |
| CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s", |
| (timeout_ms > 0) ? "timeout" : "best effort done"); |
| break; |
| } |
| |
| remain_ms = timeout_ms - (int)spent_ms; |
| if(cshutdn_wait(cshutdn, data, remain_ms)) { |
| CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted"); |
| break; |
| } |
| } |
| |
| /* Terminate any remaining. */ |
| e = Curl_llist_head(&cshutdn->list); |
| while(e) { |
| struct connectdata *conn = Curl_node_elem(e); |
| Curl_node_remove(e); |
| Curl_cshutdn_terminate(data, conn, FALSE); |
| e = Curl_llist_head(&cshutdn->list); |
| } |
| DEBUGASSERT(!Curl_llist_count(&cshutdn->list)); |
| |
| sigpipe_restore(&sigpipe_ctx); |
| } |
| |
| int Curl_cshutdn_init(struct cshutdn *cshutdn, |
| struct Curl_multi *multi) |
| { |
| DEBUGASSERT(multi); |
| cshutdn->multi = multi; |
| Curl_llist_init(&cshutdn->list, NULL); |
| cshutdn->initialised = TRUE; |
| return 0; /* good */ |
| } |
| |
| void Curl_cshutdn_destroy(struct cshutdn *cshutdn, |
| struct Curl_easy *data) |
| { |
| if(cshutdn->initialised && data) { |
| int timeout_ms = 0; |
| /* for testing, run graceful shutdown */ |
| #ifdef DEBUGBUILD |
| { |
| const char *p = getenv("CURL_GRACEFUL_SHUTDOWN"); |
| if(p) { |
| curl_off_t l; |
| if(!curlx_str_number(&p, &l, INT_MAX)) |
| timeout_ms = (int)l; |
| } |
| } |
| #endif |
| |
| CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", |
| Curl_llist_count(&cshutdn->list), timeout_ms); |
| cshutdn_terminate_all(cshutdn, data, timeout_ms); |
| } |
| cshutdn->multi = NULL; |
| } |
| |
| size_t Curl_cshutdn_count(struct Curl_easy *data) |
| { |
| if(data && data->multi) { |
| struct cshutdn *csd = &data->multi->cshutdn; |
| return Curl_llist_count(&csd->list); |
| } |
| return 0; |
| } |
| |
| size_t Curl_cshutdn_dest_count(struct Curl_easy *data, |
| const char *destination) |
| { |
| if(data && data->multi) { |
| struct cshutdn *csd = &data->multi->cshutdn; |
| size_t n = 0; |
| struct Curl_llist_node *e = Curl_llist_head(&csd->list); |
| while(e) { |
| struct connectdata *conn = Curl_node_elem(e); |
| if(!strcmp(destination, conn->destination)) |
| ++n; |
| e = Curl_node_next(e); |
| } |
| return n; |
| } |
| return 0; |
| } |
| |
| static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn, |
| struct Curl_easy *data, |
| struct connectdata *conn) |
| { |
| CURLMcode mresult; |
| |
| DEBUGASSERT(cshutdn); |
| DEBUGASSERT(cshutdn->multi->socket_cb); |
| |
| Curl_attach_connection(data, conn); |
| mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn); |
| Curl_detach_connection(data); |
| return mresult; |
| } |
| |
| void Curl_cshutdn_add(struct cshutdn *cshutdn, |
| struct connectdata *conn, |
| size_t conns_in_pool) |
| { |
| struct Curl_easy *data = cshutdn->multi->admin; |
| size_t max_total = cshutdn->multi->max_total_connections; |
| |
| /* Add the connection to our shutdown list for non-blocking shutdown |
| * during multi processing. */ |
| if(max_total > 0 && |
| (max_total <= (conns_in_pool + Curl_llist_count(&cshutdn->list)))) { |
| CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection " |
| "due to connection limit of %zu", max_total); |
| cshutdn_destroy_oldest(cshutdn, data, NULL); |
| } |
| |
| if(cshutdn->multi->socket_cb) { |
| if(cshutdn_update_ev(cshutdn, data, conn)) { |
| CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%" |
| FMT_OFF_T, conn->connection_id); |
| Curl_cshutdn_terminate(data, conn, FALSE); |
| return; |
| } |
| } |
| |
| Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node); |
| CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T |
| " to shutdowns, now %zu conns in shutdown", |
| conn->connection_id, Curl_llist_count(&cshutdn->list)); |
| } |
| |
| void Curl_cshutdn_perform(struct cshutdn *cshutdn, |
| struct Curl_easy *data, |
| struct Curl_sigpipe_ctx *sigpipe_ctx) |
| { |
| cshutdn_perform(cshutdn, data, sigpipe_ctx); |
| } |
| |
| /* return fd_set info about the shutdown connections */ |
| void Curl_cshutdn_setfds(struct cshutdn *cshutdn, |
| struct Curl_easy *data, |
| fd_set *read_fd_set, fd_set *write_fd_set, |
| int *maxfd) |
| { |
| if(Curl_llist_head(&cshutdn->list)) { |
| struct Curl_llist_node *e; |
| struct easy_pollset ps; |
| |
| Curl_pollset_init(&ps); |
| for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { |
| unsigned int i; |
| struct connectdata *conn = Curl_node_elem(e); |
| CURLcode result; |
| |
| Curl_pollset_reset(&ps); |
| Curl_attach_connection(data, conn); |
| result = Curl_conn_adjust_pollset(data, conn, &ps); |
| Curl_detach_connection(data); |
| |
| if(result) |
| continue; |
| |
| for(i = 0; i < ps.n; i++) { |
| curl_socket_t sock = ps.sockets[i]; |
| if(!FDSET_SOCK(sock)) |
| continue; |
| if(ps.actions[i] & CURL_POLL_IN) |
| FD_SET(sock, read_fd_set); |
| if(ps.actions[i] & CURL_POLL_OUT) |
| FD_SET(sock, write_fd_set); |
| if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) && |
| ((int)sock > *maxfd)) |
| *maxfd = (int)sock; |
| } |
| } |
| Curl_pollset_cleanup(&ps); |
| } |
| } |
| |
| /* return information about the shutdown connections */ |
| unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, |
| struct Curl_easy *data, |
| struct Curl_waitfds *cwfds) |
| { |
| unsigned int need = 0; |
| |
| if(Curl_llist_head(&cshutdn->list)) { |
| struct Curl_llist_node *e; |
| struct easy_pollset ps; |
| struct connectdata *conn; |
| CURLcode result; |
| |
| Curl_pollset_init(&ps); |
| for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { |
| conn = Curl_node_elem(e); |
| Curl_pollset_reset(&ps); |
| Curl_attach_connection(data, conn); |
| result = Curl_conn_adjust_pollset(data, conn, &ps); |
| Curl_detach_connection(data); |
| |
| if(!result) |
| need += Curl_waitfds_add_ps(cwfds, &ps); |
| } |
| Curl_pollset_cleanup(&ps); |
| } |
| return need; |
| } |
| |
| CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, |
| struct Curl_easy *data, |
| struct curl_pollfds *cpfds) |
| { |
| CURLcode result = CURLE_OK; |
| |
| if(Curl_llist_head(&cshutdn->list)) { |
| struct Curl_llist_node *e; |
| struct easy_pollset ps; |
| struct connectdata *conn; |
| |
| Curl_pollset_init(&ps); |
| for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { |
| conn = Curl_node_elem(e); |
| Curl_pollset_reset(&ps); |
| Curl_attach_connection(data, conn); |
| result = Curl_conn_adjust_pollset(data, conn, &ps); |
| Curl_detach_connection(data); |
| |
| if(!result) |
| result = Curl_pollfds_add_ps(cpfds, &ps); |
| if(result) { |
| Curl_pollset_cleanup(&ps); |
| Curl_pollfds_cleanup(cpfds); |
| goto out; |
| } |
| } |
| Curl_pollset_cleanup(&ps); |
| } |
| out: |
| return result; |
| } |