cshutdn.c (16600B)
1 /*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se> 9 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 10 * 11 * This software is licensed as described in the file COPYING, which 12 * you should have received as part of this distribution. The terms 13 * are also available at https://curl.se/docs/copyright.html. 14 * 15 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 16 * copies of the Software, and permit persons to whom the Software is 17 * furnished to do so, under the terms of the COPYING file. 18 * 19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 20 * KIND, either express or implied. 21 * 22 * SPDX-License-Identifier: curl 23 * 24 ***************************************************************************/ 25 26 #include "curl_setup.h" 27 28 #include <curl/curl.h> 29 30 #include "urldata.h" 31 #include "url.h" 32 #include "cfilters.h" 33 #include "progress.h" 34 #include "multiif.h" 35 #include "multi_ev.h" 36 #include "sendf.h" 37 #include "cshutdn.h" 38 #include "http_negotiate.h" 39 #include "http_ntlm.h" 40 #include "sigpipe.h" 41 #include "connect.h" 42 #include "select.h" 43 #include "curlx/strparse.h" 44 45 /* The last 3 #include files should be in this order */ 46 #include "curl_printf.h" 47 #include "curl_memory.h" 48 #include "memdebug.h" 49 50 51 static void cshutdn_run_conn_handler(struct Curl_easy *data, 52 struct connectdata *conn) 53 { 54 if(!conn->bits.shutdown_handler) { 55 56 if(conn->handler && conn->handler->disconnect) { 57 /* Some disconnect handlers do a blocking wait on server responses. 58 * FTP/IMAP/SMTP and SFTP are among them. When using the internal 59 * handle, set an overall short timeout so we do not hang for the 60 * default 120 seconds. */ 61 if(data->state.internal) { 62 data->set.timeout = DEFAULT_SHUTDOWN_TIMEOUT_MS; 63 (void)Curl_pgrsTime(data, TIMER_STARTOP); 64 } 65 66 /* This is set if protocol-specific cleanups should be made */ 67 DEBUGF(infof(data, "connection #%" FMT_OFF_T 68 ", shutdown protocol handler (aborted=%d)", 69 conn->connection_id, conn->bits.aborted)); 70 /* There are protocol handlers that block on retrieving 71 * server responses here (FTP). Set a short timeout. */ 72 conn->handler->disconnect(data, conn, conn->bits.aborted); 73 } 74 75 conn->bits.shutdown_handler = TRUE; 76 } 77 } 78 79 static void cshutdn_run_once(struct Curl_easy *data, 80 struct connectdata *conn, 81 bool *done) 82 { 83 CURLcode r1, r2; 84 bool done1, done2; 85 86 /* We expect to be attached when called */ 87 DEBUGASSERT(data->conn == conn); 88 89 cshutdn_run_conn_handler(data, conn); 90 91 if(conn->bits.shutdown_filters) { 92 *done = TRUE; 93 return; 94 } 95 96 if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET)) 97 r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1); 98 else { 99 r1 = CURLE_OK; 100 done1 = TRUE; 101 } 102 103 if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET)) 104 r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2); 105 else { 106 r2 = CURLE_OK; 107 done2 = TRUE; 108 } 109 110 /* we are done when any failed or both report success */ 111 *done = (r1 || r2 || (done1 && done2)); 112 if(*done) 113 conn->bits.shutdown_filters = TRUE; 114 } 115 116 void Curl_cshutdn_run_once(struct Curl_easy *data, 117 struct connectdata *conn, 118 bool *done) 119 { 120 DEBUGASSERT(!data->conn); 121 Curl_attach_connection(data, conn); 122 cshutdn_run_once(data, conn, done); 123 CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done); 124 Curl_detach_connection(data); 125 } 126 127 128 void Curl_cshutdn_terminate(struct Curl_easy *data, 129 struct connectdata *conn, 130 bool do_shutdown) 131 { 132 struct Curl_easy *admin = data; 133 bool done; 134 135 /* there must be a connection to close */ 136 DEBUGASSERT(conn); 137 /* it must be removed from the connection pool */ 138 DEBUGASSERT(!conn->bits.in_cpool); 139 /* the transfer must be detached from the connection */ 140 DEBUGASSERT(data && !data->conn); 141 142 /* If we can obtain an internal admin handle, use that to attach 143 * and terminate the connection. Some protocol will try to mess with 144 * `data` during shutdown and we do not want that with a `data` from 145 * the application. */ 146 if(data->multi && data->multi->admin) 147 admin = data->multi->admin; 148 149 Curl_attach_connection(admin, conn); 150 151 cshutdn_run_conn_handler(admin, conn); 152 if(do_shutdown) { 153 /* Make a last attempt to shutdown handlers and filters, if 154 * not done so already. */ 155 cshutdn_run_once(admin, conn, &done); 156 } 157 CURL_TRC_M(admin, "[SHUTDOWN] %sclosing connection #%" FMT_OFF_T, 158 conn->bits.shutdown_filters ? "" : "force ", 159 conn->connection_id); 160 Curl_conn_close(admin, SECONDARYSOCKET); 161 Curl_conn_close(admin, FIRSTSOCKET); 162 Curl_detach_connection(admin); 163 164 if(data->multi) 165 Curl_multi_ev_conn_done(data->multi, data, conn); 166 Curl_conn_free(admin, conn); 167 168 if(data->multi) { 169 CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged"); 170 Curl_multi_connchanged(data->multi); 171 } 172 } 173 174 static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn, 175 struct Curl_easy *data, 176 const char *destination) 177 { 178 struct Curl_llist_node *e; 179 struct connectdata *conn; 180 181 e = Curl_llist_head(&cshutdn->list); 182 while(e) { 183 conn = Curl_node_elem(e); 184 if(!destination || !strcmp(destination, conn->destination)) 185 break; 186 e = Curl_node_next(e); 187 } 188 189 if(e) { 190 SIGPIPE_VARIABLE(pipe_st); 191 conn = Curl_node_elem(e); 192 Curl_node_remove(e); 193 sigpipe_init(&pipe_st); 194 sigpipe_apply(data, &pipe_st); 195 Curl_cshutdn_terminate(data, conn, FALSE); 196 sigpipe_restore(&pipe_st); 197 return TRUE; 198 } 199 return FALSE; 200 } 201 202 bool Curl_cshutdn_close_oldest(struct Curl_easy *data, 203 const char *destination) 204 { 205 if(data && data->multi) { 206 struct cshutdn *csd = &data->multi->cshutdn; 207 return cshutdn_destroy_oldest(csd, data, destination); 208 } 209 return FALSE; 210 } 211 212 #define NUM_POLLS_ON_STACK 10 213 214 static CURLcode cshutdn_wait(struct cshutdn *cshutdn, 215 struct Curl_easy *data, 216 int timeout_ms) 217 { 218 struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; 219 struct curl_pollfds cpfds; 220 CURLcode result; 221 222 Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); 223 224 result = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds); 225 if(result) 226 goto out; 227 228 Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000)); 229 230 out: 231 Curl_pollfds_cleanup(&cpfds); 232 return result; 233 } 234 235 236 static void cshutdn_perform(struct cshutdn *cshutdn, 237 struct Curl_easy *data) 238 { 239 struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); 240 struct Curl_llist_node *enext; 241 struct connectdata *conn; 242 struct curltime *nowp = NULL; 243 struct curltime now; 244 timediff_t next_expire_ms = 0, ms; 245 bool done; 246 247 if(!e) 248 return; 249 250 CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections", 251 Curl_llist_count(&cshutdn->list)); 252 while(e) { 253 enext = Curl_node_next(e); 254 conn = Curl_node_elem(e); 255 Curl_cshutdn_run_once(data, conn, &done); 256 if(done) { 257 Curl_node_remove(e); 258 Curl_cshutdn_terminate(data, conn, FALSE); 259 } 260 else { 261 /* idata has one timer list, but maybe more than one connection. 262 * Set EXPIRE_SHUTDOWN to the smallest time left for all. */ 263 if(!nowp) { 264 now = curlx_now(); 265 nowp = &now; 266 } 267 ms = Curl_conn_shutdown_timeleft(conn, nowp); 268 if(ms && ms < next_expire_ms) 269 next_expire_ms = ms; 270 } 271 e = enext; 272 } 273 274 if(next_expire_ms) 275 Curl_expire_ex(data, nowp, next_expire_ms, EXPIRE_SHUTDOWN); 276 } 277 278 279 static void cshutdn_terminate_all(struct cshutdn *cshutdn, 280 struct Curl_easy *data, 281 int timeout_ms) 282 { 283 struct curltime started = curlx_now(); 284 struct Curl_llist_node *e; 285 SIGPIPE_VARIABLE(pipe_st); 286 287 DEBUGASSERT(cshutdn); 288 DEBUGASSERT(data); 289 290 CURL_TRC_M(data, "[SHUTDOWN] shutdown all"); 291 sigpipe_init(&pipe_st); 292 sigpipe_apply(data, &pipe_st); 293 294 while(Curl_llist_head(&cshutdn->list)) { 295 timediff_t timespent; 296 int remain_ms; 297 298 cshutdn_perform(cshutdn, data); 299 300 if(!Curl_llist_head(&cshutdn->list)) { 301 CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly"); 302 break; 303 } 304 305 /* wait for activity, timeout or "nothing" */ 306 timespent = curlx_timediff(curlx_now(), started); 307 if(timespent >= (timediff_t)timeout_ms) { 308 CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s", 309 (timeout_ms > 0) ? "timeout" : "best effort done"); 310 break; 311 } 312 313 remain_ms = timeout_ms - (int)timespent; 314 if(cshutdn_wait(cshutdn, data, remain_ms)) { 315 CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted"); 316 break; 317 } 318 } 319 320 /* Terminate any remaining. */ 321 e = Curl_llist_head(&cshutdn->list); 322 while(e) { 323 struct connectdata *conn = Curl_node_elem(e); 324 Curl_node_remove(e); 325 Curl_cshutdn_terminate(data, conn, FALSE); 326 e = Curl_llist_head(&cshutdn->list); 327 } 328 DEBUGASSERT(!Curl_llist_count(&cshutdn->list)); 329 330 sigpipe_restore(&pipe_st); 331 } 332 333 334 int Curl_cshutdn_init(struct cshutdn *cshutdn, 335 struct Curl_multi *multi) 336 { 337 DEBUGASSERT(multi); 338 cshutdn->multi = multi; 339 Curl_llist_init(&cshutdn->list, NULL); 340 cshutdn->initialised = TRUE; 341 return 0; /* good */ 342 } 343 344 345 void Curl_cshutdn_destroy(struct cshutdn *cshutdn, 346 struct Curl_easy *data) 347 { 348 if(cshutdn->initialised && data) { 349 int timeout_ms = 0; 350 /* Just for testing, run graceful shutdown */ 351 #ifdef DEBUGBUILD 352 { 353 const char *p = getenv("CURL_GRACEFUL_SHUTDOWN"); 354 if(p) { 355 curl_off_t l; 356 if(!curlx_str_number(&p, &l, INT_MAX)) 357 timeout_ms = (int)l; 358 } 359 } 360 #endif 361 362 CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", 363 Curl_llist_count(&cshutdn->list), timeout_ms); 364 cshutdn_terminate_all(cshutdn, data, timeout_ms); 365 } 366 cshutdn->multi = NULL; 367 } 368 369 size_t Curl_cshutdn_count(struct Curl_easy *data) 370 { 371 if(data && data->multi) { 372 struct cshutdn *csd = &data->multi->cshutdn; 373 return Curl_llist_count(&csd->list); 374 } 375 return 0; 376 } 377 378 size_t Curl_cshutdn_dest_count(struct Curl_easy *data, 379 const char *destination) 380 { 381 if(data && data->multi) { 382 struct cshutdn *csd = &data->multi->cshutdn; 383 size_t n = 0; 384 struct Curl_llist_node *e = Curl_llist_head(&csd->list); 385 while(e) { 386 struct connectdata *conn = Curl_node_elem(e); 387 if(!strcmp(destination, conn->destination)) 388 ++n; 389 e = Curl_node_next(e); 390 } 391 return n; 392 } 393 return 0; 394 } 395 396 397 static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn, 398 struct Curl_easy *data, 399 struct connectdata *conn) 400 { 401 CURLMcode mresult; 402 403 DEBUGASSERT(cshutdn); 404 DEBUGASSERT(cshutdn->multi->socket_cb); 405 406 Curl_attach_connection(data, conn); 407 mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn); 408 Curl_detach_connection(data); 409 return mresult; 410 } 411 412 413 void Curl_cshutdn_add(struct cshutdn *cshutdn, 414 struct connectdata *conn, 415 size_t conns_in_pool) 416 { 417 struct Curl_easy *data = cshutdn->multi->admin; 418 size_t max_total = (cshutdn->multi->max_total_connections > 0) ? 419 (size_t)cshutdn->multi->max_total_connections : 0; 420 421 /* Add the connection to our shutdown list for non-blocking shutdown 422 * during multi processing. */ 423 if(max_total > 0 && (max_total <= 424 (conns_in_pool + Curl_llist_count(&cshutdn->list)))) { 425 CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection " 426 "due to connection limit of %zu", max_total); 427 cshutdn_destroy_oldest(cshutdn, data, NULL); 428 } 429 430 if(cshutdn->multi->socket_cb) { 431 if(cshutdn_update_ev(cshutdn, data, conn)) { 432 CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%" 433 FMT_OFF_T, conn->connection_id); 434 Curl_cshutdn_terminate(data, conn, FALSE); 435 return; 436 } 437 } 438 439 Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node); 440 CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T 441 " to shutdowns, now %zu conns in shutdown", 442 conn->connection_id, Curl_llist_count(&cshutdn->list)); 443 } 444 445 446 static void cshutdn_multi_socket(struct cshutdn *cshutdn, 447 struct Curl_easy *data, 448 curl_socket_t s) 449 { 450 struct Curl_llist_node *e; 451 struct connectdata *conn; 452 bool done; 453 454 DEBUGASSERT(cshutdn->multi->socket_cb); 455 e = Curl_llist_head(&cshutdn->list); 456 while(e) { 457 conn = Curl_node_elem(e); 458 if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) { 459 Curl_cshutdn_run_once(data, conn, &done); 460 if(done || cshutdn_update_ev(cshutdn, data, conn)) { 461 Curl_node_remove(e); 462 Curl_cshutdn_terminate(data, conn, FALSE); 463 } 464 break; 465 } 466 e = Curl_node_next(e); 467 } 468 } 469 470 471 void Curl_cshutdn_perform(struct cshutdn *cshutdn, 472 struct Curl_easy *data, 473 curl_socket_t s) 474 { 475 if((s == CURL_SOCKET_TIMEOUT) || (!cshutdn->multi->socket_cb)) 476 cshutdn_perform(cshutdn, data); 477 else 478 cshutdn_multi_socket(cshutdn, data, s); 479 } 480 481 /* return fd_set info about the shutdown connections */ 482 void Curl_cshutdn_setfds(struct cshutdn *cshutdn, 483 struct Curl_easy *data, 484 fd_set *read_fd_set, fd_set *write_fd_set, 485 int *maxfd) 486 { 487 if(Curl_llist_head(&cshutdn->list)) { 488 struct Curl_llist_node *e; 489 490 for(e = Curl_llist_head(&cshutdn->list); e; 491 e = Curl_node_next(e)) { 492 struct easy_pollset ps; 493 unsigned int i; 494 struct connectdata *conn = Curl_node_elem(e); 495 memset(&ps, 0, sizeof(ps)); 496 Curl_attach_connection(data, conn); 497 Curl_conn_adjust_pollset(data, conn, &ps); 498 Curl_detach_connection(data); 499 500 for(i = 0; i < ps.num; i++) { 501 #if defined(__DJGPP__) 502 #pragma GCC diagnostic push 503 #pragma GCC diagnostic ignored "-Warith-conversion" 504 #endif 505 if(ps.actions[i] & CURL_POLL_IN) 506 FD_SET(ps.sockets[i], read_fd_set); 507 if(ps.actions[i] & CURL_POLL_OUT) 508 FD_SET(ps.sockets[i], write_fd_set); 509 #if defined(__DJGPP__) 510 #pragma GCC diagnostic pop 511 #endif 512 if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) && 513 ((int)ps.sockets[i] > *maxfd)) 514 *maxfd = (int)ps.sockets[i]; 515 } 516 } 517 } 518 } 519 520 /* return information about the shutdown connections */ 521 unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, 522 struct Curl_easy *data, 523 struct Curl_waitfds *cwfds) 524 { 525 unsigned int need = 0; 526 527 if(Curl_llist_head(&cshutdn->list)) { 528 struct Curl_llist_node *e; 529 struct easy_pollset ps; 530 struct connectdata *conn; 531 532 for(e = Curl_llist_head(&cshutdn->list); e; 533 e = Curl_node_next(e)) { 534 conn = Curl_node_elem(e); 535 memset(&ps, 0, sizeof(ps)); 536 Curl_attach_connection(data, conn); 537 Curl_conn_adjust_pollset(data, conn, &ps); 538 Curl_detach_connection(data); 539 540 need += Curl_waitfds_add_ps(cwfds, &ps); 541 } 542 } 543 return need; 544 } 545 546 CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, 547 struct Curl_easy *data, 548 struct curl_pollfds *cpfds) 549 { 550 CURLcode result = CURLE_OK; 551 552 if(Curl_llist_head(&cshutdn->list)) { 553 struct Curl_llist_node *e; 554 struct easy_pollset ps; 555 struct connectdata *conn; 556 557 for(e = Curl_llist_head(&cshutdn->list); e; 558 e = Curl_node_next(e)) { 559 conn = Curl_node_elem(e); 560 memset(&ps, 0, sizeof(ps)); 561 Curl_attach_connection(data, conn); 562 Curl_conn_adjust_pollset(data, conn, &ps); 563 Curl_detach_connection(data); 564 565 result = Curl_pollfds_add_ps(cpfds, &ps); 566 if(result) { 567 Curl_pollfds_cleanup(cpfds); 568 goto out; 569 } 570 } 571 } 572 out: 573 return result; 574 }