Coverage Report

Created: 2026-07-16 06:10

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