quickjs-tart

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

multi_ev.c (19877B)


      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
      9  *
     10  * This software is licensed as described in the file COPYING, which
     11  * you should have received as part of this distribution. The terms
     12  * are also available at https://curl.se/docs/copyright.html.
     13  *
     14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     15  * copies of the Software, and permit persons to whom the Software is
     16  * furnished to do so, under the terms of the COPYING file.
     17  *
     18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     19  * KIND, either express or implied.
     20  *
     21  * SPDX-License-Identifier: curl
     22  *
     23  ***************************************************************************/
     24 
     25 #include "curl_setup.h"
     26 
     27 #include <curl/curl.h>
     28 
     29 #include "urldata.h"
     30 #include "url.h"
     31 #include "cfilters.h"
     32 #include "curl_trc.h"
     33 #include "multiif.h"
     34 #include "curlx/timeval.h"
     35 #include "multi_ev.h"
     36 #include "select.h"
     37 #include "uint-bset.h"
     38 #include "uint-spbset.h"
     39 #include "uint-table.h"
     40 #include "curlx/warnless.h"
     41 #include "multihandle.h"
     42 #include "socks.h"
     43 /* The last 3 #include files should be in this order */
     44 #include "curl_printf.h"
     45 #include "curl_memory.h"
     46 #include "memdebug.h"
     47 
     48 
     49 static void mev_in_callback(struct Curl_multi *multi, bool value)
     50 {
     51   multi->in_callback = value;
     52 }
     53 
     54 #define CURL_MEV_CONN_HASH_SIZE 3
     55 
     56 /* Information about a socket for which we inform the libcurl application
     57  * what to supervise (CURL_POLL_IN/CURL_POLL_OUT/CURL_POLL_REMOVE)
     58  */
     59 struct mev_sh_entry {
     60   struct uint_spbset xfers; /* bitset of transfers `mid`s on this socket */
     61   struct connectdata *conn; /* connection using this socket or NULL */
     62   void *user_data;      /* libcurl app data via curl_multi_assign() */
     63   unsigned int action;  /* CURL_POLL_IN/CURL_POLL_OUT we last told the
     64                          * libcurl application to watch out for */
     65   unsigned int readers; /* this many transfers want to read */
     66   unsigned int writers; /* this many transfers want to write */
     67 };
     68 
     69 static size_t mev_sh_entry_hash(void *key, size_t key_length, size_t slots_num)
     70 {
     71   curl_socket_t fd = *((curl_socket_t *) key);
     72   (void) key_length;
     73   return (fd % (curl_socket_t)slots_num);
     74 }
     75 
     76 static size_t mev_sh_entry_compare(void *k1, size_t k1_len,
     77                                    void *k2, size_t k2_len)
     78 {
     79   (void) k1_len; (void) k2_len;
     80   return (*((curl_socket_t *) k1)) == (*((curl_socket_t *) k2));
     81 }
     82 
     83 /* sockhash entry destructor callback */
     84 static void mev_sh_entry_dtor(void *freethis)
     85 {
     86   struct mev_sh_entry *entry = (struct mev_sh_entry *)freethis;
     87   Curl_uint_spbset_destroy(&entry->xfers);
     88   free(entry);
     89 }
     90 
     91 /* look up a given socket in the socket hash, skip invalid sockets */
     92 static struct mev_sh_entry *
     93 mev_sh_entry_get(struct Curl_hash *sh, curl_socket_t s)
     94 {
     95   if(s != CURL_SOCKET_BAD) {
     96     /* only look for proper sockets */
     97     return Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t));
     98   }
     99   return NULL;
    100 }
    101 
    102 /* make sure this socket is present in the hash for this handle */
    103 static struct mev_sh_entry *
    104 mev_sh_entry_add(struct Curl_hash *sh, curl_socket_t s)
    105 {
    106   struct mev_sh_entry *there = mev_sh_entry_get(sh, s);
    107   struct mev_sh_entry *check;
    108 
    109   if(there) {
    110     /* it is present, return fine */
    111     return there;
    112   }
    113 
    114   /* not present, add it */
    115   check = calloc(1, sizeof(struct mev_sh_entry));
    116   if(!check)
    117     return NULL; /* major failure */
    118 
    119   Curl_uint_spbset_init(&check->xfers);
    120 
    121   /* make/add new hash entry */
    122   if(!Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) {
    123     mev_sh_entry_dtor(check);
    124     return NULL; /* major failure */
    125   }
    126 
    127   return check; /* things are good in sockhash land */
    128 }
    129 
    130 /* delete the given socket entry from the hash */
    131 static void mev_sh_entry_kill(struct Curl_multi *multi, curl_socket_t s)
    132 {
    133   Curl_hash_delete(&multi->ev.sh_entries, (char *)&s, sizeof(curl_socket_t));
    134 }
    135 
    136 static size_t mev_sh_entry_user_count(struct mev_sh_entry *e)
    137 {
    138   return Curl_uint_spbset_count(&e->xfers) + (e->conn ? 1 : 0);
    139 }
    140 
    141 static bool mev_sh_entry_xfer_known(struct mev_sh_entry *e,
    142                                     struct Curl_easy *data)
    143 {
    144   return Curl_uint_spbset_contains(&e->xfers, data->mid);
    145 }
    146 
    147 static bool mev_sh_entry_conn_known(struct mev_sh_entry *e,
    148                                     struct connectdata *conn)
    149 {
    150   return (e->conn == conn);
    151 }
    152 
    153 static bool mev_sh_entry_xfer_add(struct mev_sh_entry *e,
    154                                   struct Curl_easy *data)
    155 {
    156    /* detect weird values */
    157   DEBUGASSERT(mev_sh_entry_user_count(e) < 100000);
    158   return Curl_uint_spbset_add(&e->xfers, data->mid);
    159 }
    160 
    161 static bool mev_sh_entry_conn_add(struct mev_sh_entry *e,
    162                                   struct connectdata *conn)
    163 {
    164    /* detect weird values */
    165   DEBUGASSERT(mev_sh_entry_user_count(e) < 100000);
    166   DEBUGASSERT(!e->conn);
    167   if(e->conn)
    168     return FALSE;
    169   e->conn = conn;
    170   return TRUE;
    171 }
    172 
    173 
    174 static bool mev_sh_entry_xfer_remove(struct mev_sh_entry *e,
    175                                      struct Curl_easy *data)
    176 {
    177   bool present = Curl_uint_spbset_contains(&e->xfers, data->mid);
    178   if(present)
    179     Curl_uint_spbset_remove(&e->xfers, data->mid);
    180   return present;
    181 }
    182 
    183 static bool mev_sh_entry_conn_remove(struct mev_sh_entry *e,
    184                                      struct connectdata *conn)
    185 {
    186   DEBUGASSERT(e->conn == conn);
    187   if(e->conn == conn) {
    188     e->conn = NULL;
    189     return TRUE;
    190   }
    191   return FALSE;
    192 }
    193 
    194 /* Purge any information about socket `s`.
    195  * Let the socket callback know as well when necessary */
    196 static CURLMcode mev_forget_socket(struct Curl_multi *multi,
    197                                    struct Curl_easy *data,
    198                                    curl_socket_t s,
    199                                    const char *cause)
    200 {
    201   struct mev_sh_entry *entry = mev_sh_entry_get(&multi->ev.sh_entries, s);
    202   int rc = 0;
    203 
    204   if(!entry) /* we never knew or already forgot about this socket */
    205     return CURLM_OK;
    206 
    207   /* We managed this socket before, tell the socket callback to forget it. */
    208   if(multi->socket_cb) {
    209     CURL_TRC_M(data, "ev %s, call(fd=%" FMT_SOCKET_T ", ev=REMOVE)",
    210                cause, s);
    211     mev_in_callback(multi, TRUE);
    212     rc = multi->socket_cb(data, s, CURL_POLL_REMOVE,
    213                           multi->socket_userp, entry->user_data);
    214     mev_in_callback(multi, FALSE);
    215   }
    216 
    217   mev_sh_entry_kill(multi, s);
    218   if(rc == -1) {
    219     multi->dead = TRUE;
    220     return CURLM_ABORTED_BY_CALLBACK;
    221   }
    222   return CURLM_OK;
    223 }
    224 
    225 static CURLMcode mev_sh_entry_update(struct Curl_multi *multi,
    226                                      struct Curl_easy *data,
    227                                      struct mev_sh_entry *entry,
    228                                      curl_socket_t s,
    229                                      unsigned char last_action,
    230                                      unsigned char cur_action)
    231 {
    232   int rc, comboaction;
    233 
    234   /* we should only be called when the callback exists */
    235   DEBUGASSERT(multi->socket_cb);
    236   if(!multi->socket_cb)
    237     return CURLM_OK;
    238 
    239   /* Transfer `data` goes from `last_action` to `cur_action` on socket `s`
    240    * with `multi->ev.sh_entries` entry `entry`. Update `entry` and trigger
    241    * `multi->socket_cb` on change, if the callback is set. */
    242   if(last_action == cur_action)  /* nothing from `data` changed */
    243     return CURLM_OK;
    244 
    245   if(last_action & CURL_POLL_IN) {
    246     DEBUGASSERT(entry->readers);
    247     if(!(cur_action & CURL_POLL_IN))
    248       entry->readers--;
    249   }
    250   else if(cur_action & CURL_POLL_IN)
    251     entry->readers++;
    252 
    253   if(last_action & CURL_POLL_OUT) {
    254     DEBUGASSERT(entry->writers);
    255     if(!(cur_action & CURL_POLL_OUT))
    256       entry->writers--;
    257   }
    258   else if(cur_action & CURL_POLL_OUT)
    259     entry->writers++;
    260 
    261   DEBUGASSERT(entry->readers <= mev_sh_entry_user_count(entry));
    262   DEBUGASSERT(entry->writers <= mev_sh_entry_user_count(entry));
    263   DEBUGASSERT(entry->writers + entry->readers);
    264 
    265   CURL_TRC_M(data, "ev update fd=%" FMT_SOCKET_T ", action '%s%s' -> '%s%s'"
    266              " (%d/%d r/w)", s,
    267              (last_action & CURL_POLL_IN) ? "IN" : "",
    268              (last_action & CURL_POLL_OUT) ? "OUT" : "",
    269              (cur_action & CURL_POLL_IN) ? "IN" : "",
    270              (cur_action & CURL_POLL_OUT) ? "OUT" : "",
    271              entry->readers, entry->writers);
    272 
    273   comboaction = (entry->writers ? CURL_POLL_OUT : 0) |
    274                 (entry->readers ? CURL_POLL_IN : 0);
    275   if(((int)entry->action == comboaction)) /* nothing for socket changed */
    276     return CURLM_OK;
    277 
    278   CURL_TRC_M(data, "ev update call(fd=%" FMT_SOCKET_T ", ev=%s%s)",
    279              s, (comboaction & CURL_POLL_IN) ? "IN" : "",
    280              (comboaction & CURL_POLL_OUT) ? "OUT" : "");
    281   mev_in_callback(multi, TRUE);
    282   rc = multi->socket_cb(data, s, comboaction, multi->socket_userp,
    283                         entry->user_data);
    284 
    285   mev_in_callback(multi, FALSE);
    286   if(rc == -1) {
    287     multi->dead = TRUE;
    288     return CURLM_ABORTED_BY_CALLBACK;
    289   }
    290   entry->action = (unsigned int)comboaction;
    291   return CURLM_OK;
    292 }
    293 
    294 static CURLMcode mev_pollset_diff(struct Curl_multi *multi,
    295                                   struct Curl_easy *data,
    296                                   struct connectdata *conn,
    297                                   struct easy_pollset *ps,
    298                                   struct easy_pollset *prev_ps)
    299 {
    300   struct mev_sh_entry *entry;
    301   curl_socket_t s;
    302   unsigned int i, j;
    303   CURLMcode mresult;
    304 
    305   /* The transfer `data` reports in `ps` the sockets it is interested
    306    * in and which combination of CURL_POLL_IN/CURL_POLL_OUT it wants
    307    * to have monitored for events.
    308    * There can be more than 1 transfer interested in the same socket
    309    * and 1 transfer might be interested in more than 1 socket.
    310    * `prev_ps` is the pollset copy from the previous call here. On
    311    * the 1st call it will be empty.
    312    */
    313   DEBUGASSERT(ps);
    314   DEBUGASSERT(prev_ps);
    315 
    316   /* Handle changes to sockets the transfer is interested in. */
    317   for(i = 0; i < ps->num; i++) {
    318     unsigned char last_action;
    319     bool first_time = FALSE; /* data/conn appears first time on socket */
    320 
    321     s = ps->sockets[i];
    322     /* Have we handled this socket before? */
    323     entry = mev_sh_entry_get(&multi->ev.sh_entries, s);
    324     if(!entry) {
    325       /* new socket, add new entry */
    326       first_time = TRUE;
    327       entry = mev_sh_entry_add(&multi->ev.sh_entries, s);
    328       if(!entry) /* fatal */
    329         return CURLM_OUT_OF_MEMORY;
    330       CURL_TRC_M(data, "ev new entry fd=%" FMT_SOCKET_T, s);
    331     }
    332     else if(conn) {
    333       first_time = !mev_sh_entry_conn_known(entry, conn);
    334     }
    335     else {
    336       first_time = !mev_sh_entry_xfer_known(entry, data);
    337     }
    338 
    339     /* What was the previous action the transfer had regarding this socket?
    340      * If the transfer is new to the socket, disregard the information
    341      * in `last_poll`, because the socket might have been destroyed and
    342      * reopened. We'd have cleared the sh_entry for that, but the socket
    343      * might still be mentioned in the hashed pollsets. */
    344     last_action = 0;
    345     if(first_time) {
    346       if(conn) {
    347         if(!mev_sh_entry_conn_add(entry, conn))
    348           return CURLM_OUT_OF_MEMORY;
    349       }
    350       else {
    351         if(!mev_sh_entry_xfer_add(entry, data))
    352           return CURLM_OUT_OF_MEMORY;
    353       }
    354       CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", added %s #%" FMT_OFF_T
    355                  ", total=%u/%d (xfer/conn)", s,
    356                  conn ? "connection" : "transfer",
    357                  conn ? conn->connection_id : data->mid,
    358                  Curl_uint_spbset_count(&entry->xfers),
    359                  entry->conn ? 1 : 0);
    360     }
    361     else {
    362       for(j = 0; j < prev_ps->num; j++) {
    363         if(s == prev_ps->sockets[j]) {
    364           last_action = prev_ps->actions[j];
    365           break;
    366         }
    367       }
    368     }
    369     /* track readers/writers changes and report to socket callback */
    370     mresult = mev_sh_entry_update(multi, data, entry, s,
    371                                   last_action, ps->actions[i]);
    372     if(mresult)
    373       return mresult;
    374   }
    375 
    376   /* Handle changes to sockets the transfer is NO LONGER interested in. */
    377   for(i = 0; i < prev_ps->num; i++) {
    378     bool stillused = FALSE;
    379 
    380     s = prev_ps->sockets[i];
    381     for(j = 0; j < ps->num; j++) {
    382       if(s == ps->sockets[j]) {
    383         /* socket is still supervised */
    384         stillused = TRUE;
    385         break;
    386       }
    387     }
    388     if(stillused)
    389       continue;
    390 
    391     entry = mev_sh_entry_get(&multi->ev.sh_entries, s);
    392     /* if entry does not exist, we were either never told about it or
    393      * have already cleaned up this socket via Curl_multi_ev_socket_done().
    394      * In other words: this is perfectly normal */
    395     if(!entry)
    396       continue;
    397 
    398     if(conn && !mev_sh_entry_conn_remove(entry, conn)) {
    399       /* `conn` says in `prev_ps` that it had been using a socket,
    400        * but `conn` has not been registered for it.
    401        * This should not happen if our book-keeping is correct? */
    402       CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", conn lost "
    403                  "interest but is not registered", s);
    404       DEBUGASSERT(NULL);
    405       continue;
    406     }
    407 
    408     if(!conn && !mev_sh_entry_xfer_remove(entry, data)) {
    409       /* `data` says in `prev_ps` that it had been using a socket,
    410        * but `data` has not been registered for it.
    411        * This should not happen if our book-keeping is correct? */
    412       CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", transfer lost "
    413                  "interest but is not registered", s);
    414       DEBUGASSERT(NULL);
    415       continue;
    416     }
    417 
    418     if(mev_sh_entry_user_count(entry)) {
    419       /* track readers/writers changes and report to socket callback */
    420       mresult = mev_sh_entry_update(multi, data, entry, s,
    421                                     prev_ps->actions[i], 0);
    422       if(mresult)
    423         return mresult;
    424       CURL_TRC_M(data, "ev entry fd=%" FMT_SOCKET_T ", removed transfer, "
    425                  "total=%u/%d (xfer/conn)", s,
    426                  Curl_uint_spbset_count(&entry->xfers),
    427                  entry->conn ? 1 : 0);
    428     }
    429     else {
    430       mresult = mev_forget_socket(multi, data, s, "last user gone");
    431       if(mresult)
    432         return mresult;
    433     }
    434   } /* for loop over num */
    435 
    436   /* Remember for next time */
    437   memcpy(prev_ps, ps, sizeof(*prev_ps));
    438   return CURLM_OK;
    439 }
    440 
    441 static void mev_pollset_dtor(void *key, size_t klen, void *entry)
    442 {
    443   (void)key;
    444   (void)klen;
    445   free(entry);
    446 }
    447 
    448 static struct easy_pollset*
    449 mev_add_new_conn_pollset(struct connectdata *conn)
    450 {
    451   struct easy_pollset *ps;
    452 
    453   ps = calloc(1, sizeof(*ps));
    454   if(!ps)
    455     return NULL;
    456   if(Curl_conn_meta_set(conn, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor))
    457     return NULL;
    458   return ps;
    459 }
    460 
    461 static struct easy_pollset*
    462 mev_add_new_xfer_pollset(struct Curl_easy *data)
    463 {
    464   struct easy_pollset *ps;
    465 
    466   ps = calloc(1, sizeof(*ps));
    467   if(!ps)
    468     return NULL;
    469   if(Curl_meta_set(data, CURL_META_MEV_POLLSET, ps, mev_pollset_dtor))
    470     return NULL;
    471   return ps;
    472 }
    473 
    474 static struct easy_pollset *
    475 mev_get_last_pollset(struct Curl_easy *data,
    476                      struct connectdata *conn)
    477 {
    478   if(data) {
    479     if(conn)
    480       return Curl_conn_meta_get(conn, CURL_META_MEV_POLLSET);
    481     return Curl_meta_get(data, CURL_META_MEV_POLLSET);
    482   }
    483   return NULL;
    484 }
    485 
    486 static void mev_init_cur_pollset(struct easy_pollset *ps,
    487                                  struct Curl_easy *data,
    488                                  struct connectdata *conn)
    489 {
    490   memset(ps, 0, sizeof(*ps));
    491   if(conn)
    492     Curl_conn_adjust_pollset(data, conn, ps);
    493   else if(data)
    494     Curl_multi_getsock(data, ps, "ev assess");
    495 }
    496 
    497 static CURLMcode mev_assess(struct Curl_multi *multi,
    498                             struct Curl_easy *data,
    499                             struct connectdata *conn)
    500 {
    501   if(multi && multi->socket_cb) {
    502     struct easy_pollset ps, *last_ps;
    503 
    504     mev_init_cur_pollset(&ps, data, conn);
    505     last_ps = mev_get_last_pollset(data, conn);
    506 
    507     if(!last_ps && ps.num) {
    508       if(conn)
    509         last_ps = mev_add_new_conn_pollset(conn);
    510       else
    511         last_ps = mev_add_new_xfer_pollset(data);
    512       if(!last_ps)
    513         return CURLM_OUT_OF_MEMORY;
    514     }
    515 
    516     if(last_ps)
    517       return mev_pollset_diff(multi, data, conn, &ps, last_ps);
    518     else
    519       DEBUGASSERT(!ps.num);
    520   }
    521   return CURLM_OK;
    522 }
    523 
    524 CURLMcode Curl_multi_ev_assess_xfer(struct Curl_multi *multi,
    525                                     struct Curl_easy *data)
    526 {
    527   return mev_assess(multi, data, NULL);
    528 }
    529 
    530 CURLMcode Curl_multi_ev_assess_conn(struct Curl_multi *multi,
    531                                     struct Curl_easy *data,
    532                                     struct connectdata *conn)
    533 {
    534   return mev_assess(multi, data, conn);
    535 }
    536 
    537 CURLMcode Curl_multi_ev_assess_xfer_bset(struct Curl_multi *multi,
    538                                          struct uint_bset *set)
    539 {
    540   unsigned int mid;
    541   CURLMcode result = CURLM_OK;
    542 
    543   if(multi && multi->socket_cb && Curl_uint_bset_first(set, &mid)) {
    544     do {
    545       struct Curl_easy *data = Curl_multi_get_easy(multi, mid);
    546       if(data)
    547         result = Curl_multi_ev_assess_xfer(multi, data);
    548     }
    549     while(!result && Curl_uint_bset_next(set, mid, &mid));
    550   }
    551   return result;
    552 }
    553 
    554 
    555 CURLMcode Curl_multi_ev_assign(struct Curl_multi *multi,
    556                                curl_socket_t s,
    557                                void *user_data)
    558 {
    559   struct mev_sh_entry *e = mev_sh_entry_get(&multi->ev.sh_entries, s);
    560   if(!e)
    561     return CURLM_BAD_SOCKET;
    562   e->user_data = user_data;
    563   return CURLM_OK;
    564 }
    565 
    566 void Curl_multi_ev_dirty_xfers(struct Curl_multi *multi,
    567                                curl_socket_t s,
    568                                bool *run_cpool)
    569 {
    570   struct mev_sh_entry *entry;
    571 
    572   DEBUGASSERT(s != CURL_SOCKET_TIMEOUT);
    573   entry = mev_sh_entry_get(&multi->ev.sh_entries, s);
    574 
    575   /* Unmatched socket, we cannot act on it but we ignore this fact. In
    576      real-world tests it has been proved that libevent can in fact give
    577      the application actions even though the socket was just previously
    578      asked to get removed, so thus we better survive stray socket actions
    579      and just move on. */
    580   if(entry) {
    581     struct Curl_easy *data;
    582     unsigned int mid;
    583 
    584     if(Curl_uint_spbset_first(&entry->xfers, &mid)) {
    585       do {
    586         data = Curl_multi_get_easy(multi, mid);
    587         if(data) {
    588           Curl_multi_mark_dirty(data);
    589         }
    590         else {
    591           CURL_TRC_M(multi->admin, "socket transfer %u no longer found", mid);
    592           Curl_uint_spbset_remove(&entry->xfers, mid);
    593         }
    594       }
    595       while(Curl_uint_spbset_next(&entry->xfers, mid, &mid));
    596     }
    597 
    598     if(entry->conn)
    599       *run_cpool = TRUE;
    600   }
    601 }
    602 
    603 void Curl_multi_ev_socket_done(struct Curl_multi *multi,
    604                                struct Curl_easy *data, curl_socket_t s)
    605 {
    606   mev_forget_socket(multi, data, s, "socket done");
    607 }
    608 
    609 void Curl_multi_ev_xfer_done(struct Curl_multi *multi,
    610                              struct Curl_easy *data)
    611 {
    612   DEBUGASSERT(!data->conn); /* transfer should have been detached */
    613   if(data != multi->admin) {
    614     (void)mev_assess(multi, data, NULL);
    615     Curl_meta_remove(data, CURL_META_MEV_POLLSET);
    616   }
    617 }
    618 
    619 void Curl_multi_ev_conn_done(struct Curl_multi *multi,
    620                              struct Curl_easy *data,
    621                              struct connectdata *conn)
    622 {
    623   (void)mev_assess(multi, data, conn);
    624   Curl_conn_meta_remove(conn, CURL_META_MEV_POLLSET);
    625 }
    626 
    627 #define CURL_MEV_PS_HASH_SLOTS   (991)  /* nice prime */
    628 
    629 void Curl_multi_ev_init(struct Curl_multi *multi, size_t hashsize)
    630 {
    631   Curl_hash_init(&multi->ev.sh_entries, hashsize, mev_sh_entry_hash,
    632                  mev_sh_entry_compare, mev_sh_entry_dtor);
    633 }
    634 
    635 void Curl_multi_ev_cleanup(struct Curl_multi *multi)
    636 {
    637   Curl_hash_destroy(&multi->ev.sh_entries);
    638 }