quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

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 }