Coverage Report

Created: 2026-09-01 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/thrdqueue.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
#ifdef USE_THREADS
27
28
#include "llist.h"
29
#include "curl_threads.h"
30
#include "thrdpool.h"
31
#include "thrdqueue.h"
32
#include "curlx/timeval.h"
33
#ifdef CURLVERBOSE
34
#include "curl_trc.h"
35
#include "urldata.h"
36
#endif
37
38
39
struct curl_thrdq {
40
  const char *name;
41
  curl_mutex_t lock;
42
  curl_cond_t await;
43
  struct Curl_llist sendq;
44
  struct Curl_llist recvq;
45
  struct curl_thrdpool *tpool;
46
  Curl_thrdq_item_free_cb *fn_free;
47
  Curl_thrdq_item_process_cb *fn_process;
48
  Curl_thrdq_ev_cb *fn_event;
49
  void *fn_user_data;
50
  BIT(aborted);
51
};
52
53
struct thrdq_item {
54
  struct Curl_llist_node node;
55
  Curl_thrdq_item_free_cb *fn_free;
56
  Curl_thrdq_item_process_cb *fn_process;
57
  void *item;
58
  struct curltime start;
59
  timediff_t timeout_ms;
60
  const char *description;
61
};
62
63
static struct thrdq_item *thrdq_item_create(struct curl_thrdq *tqueue,
64
                                            void *item,
65
                                            const char *description,
66
                                            timediff_t timeout_ms)
67
0
{
68
0
  struct thrdq_item *qitem;
69
70
0
  qitem = curlx_calloc(1, sizeof(*qitem));
71
0
  if(!qitem)
72
0
    return NULL;
73
0
  qitem->item = item;
74
0
  qitem->description = description;
75
0
  qitem->fn_free = tqueue->fn_free;
76
0
  qitem->fn_process = tqueue->fn_process;
77
0
  if(timeout_ms) {
78
0
    qitem->start = curlx_now();
79
0
    qitem->timeout_ms = timeout_ms;
80
0
  }
81
0
  return qitem;
82
0
}
83
84
static void thrdq_item_destroy(struct thrdq_item *qitem)
85
0
{
86
0
  if(qitem->item)
87
0
    qitem->fn_free(qitem->item);
88
0
  curlx_free(qitem);
89
0
}
90
91
static void thrdq_item_list_dtor(void *user_data, void *elem)
92
0
{
93
0
  (void)user_data;
94
0
  thrdq_item_destroy(elem);
95
0
}
96
97
static void *thrdq_tpool_take(void *user_data, const char **pdescription,
98
                              timediff_t *ptimeout_ms)
99
0
{
100
0
  struct curl_thrdq *tqueue = user_data;
101
0
  struct thrdq_item *qitem = NULL;
102
0
  struct Curl_llist_node *e;
103
0
  Curl_thrdq_ev_cb *fn_event = NULL;
104
0
  void *fn_user_data = NULL;
105
106
0
  Curl_mutex_acquire(&tqueue->lock);
107
0
  *pdescription = NULL;
108
0
  *ptimeout_ms = 0;
109
0
  if(!tqueue->aborted) {
110
0
    e = Curl_llist_head(&tqueue->sendq);
111
0
    if(e) {
112
0
      struct curltime now = curlx_now();
113
0
      timediff_t timeout_ms;
114
0
      while(e) {
115
0
        qitem = Curl_node_take_elem(e);
116
0
        timeout_ms = (!qitem->timeout_ms) ? 0 :
117
0
          (qitem->timeout_ms - curlx_ptimediff_ms(&now, &qitem->start));
118
0
        if(timeout_ms < 0) {
119
          /* timed out while queued, place on receive queue */
120
0
          Curl_llist_append(&tqueue->recvq, qitem, &qitem->node);
121
0
          fn_event = tqueue->fn_event;
122
0
          fn_user_data = tqueue->fn_user_data;
123
0
          qitem = NULL;
124
0
          e = Curl_llist_head(&tqueue->sendq);
125
0
          continue;
126
0
        }
127
0
        else {
128
0
          *pdescription = qitem->description;
129
0
          *ptimeout_ms = timeout_ms;
130
0
          break;
131
0
        }
132
0
      }
133
0
    }
134
0
  }
135
0
  Curl_mutex_release(&tqueue->lock);
136
  /* avoiding deadlocks */
137
0
  if(fn_event)
138
0
    fn_event(tqueue, CURL_THRDQ_EV_ITEM_DONE, fn_user_data);
139
0
  return qitem;
140
0
}
141
142
static void thrdq_tpool_return(void *item, void *user_data)
143
0
{
144
0
  struct curl_thrdq *tqueue = user_data;
145
0
  struct thrdq_item *qitem = item;
146
0
  Curl_thrdq_ev_cb *fn_event = NULL;
147
0
  void *fn_user_data = NULL;
148
149
0
  if(!tqueue) {
150
0
    thrdq_item_destroy(item);
151
0
    return;
152
0
  }
153
154
0
  Curl_mutex_acquire(&tqueue->lock);
155
0
  if(tqueue->aborted) {
156
0
    thrdq_item_destroy(qitem);
157
0
  }
158
0
  else {
159
0
    DEBUGASSERT(!Curl_node_llist(&qitem->node));
160
0
    Curl_llist_append(&tqueue->recvq, qitem, &qitem->node);
161
0
    fn_event = tqueue->fn_event;
162
0
    fn_user_data = tqueue->fn_user_data;
163
0
  }
164
0
  Curl_mutex_release(&tqueue->lock);
165
  /* avoiding deadlocks */
166
0
  if(fn_event)
167
0
    fn_event(tqueue, CURL_THRDQ_EV_ITEM_DONE, fn_user_data);
168
0
}
169
170
static void thrdq_tpool_process(void *item)
171
0
{
172
0
  struct thrdq_item *qitem = item;
173
0
  qitem->fn_process(qitem->item);
174
0
}
175
176
static void thrdq_unlink(struct curl_thrdq *tqueue, bool locked, bool join)
177
9.81k
{
178
9.81k
  DEBUGASSERT(tqueue->aborted);
179
9.81k
  if(tqueue->tpool) {
180
9.81k
    if(locked)
181
9.81k
      Curl_mutex_release(&tqueue->lock);
182
9.81k
    Curl_thrdpool_destroy(tqueue->tpool, join);
183
9.81k
    tqueue->tpool = NULL;
184
9.81k
    if(locked)
185
9.81k
      Curl_mutex_acquire(&tqueue->lock);
186
9.81k
  }
187
188
9.81k
  Curl_llist_destroy(&tqueue->sendq, NULL);
189
9.81k
  Curl_llist_destroy(&tqueue->recvq, NULL);
190
9.81k
  Curl_cond_destroy(&tqueue->await);
191
9.81k
  if(locked)
192
9.81k
    Curl_mutex_release(&tqueue->lock);
193
9.81k
  Curl_mutex_destroy(&tqueue->lock);
194
9.81k
  curlx_free(tqueue);
195
9.81k
}
196
197
CURLcode Curl_thrdq_create(struct curl_thrdq **ptqueue,
198
                           const char *name,
199
                           uint32_t min_threads,
200
                           uint32_t max_threads,
201
                           uint32_t idle_time_ms,
202
                           Curl_thrdq_item_free_cb *fn_free,
203
                           Curl_thrdq_item_process_cb *fn_process,
204
                           Curl_thrdq_ev_cb *fn_event,
205
                           void *user_data)
206
9.81k
{
207
9.81k
  struct curl_thrdq *tqueue;
208
9.81k
  CURLcode result = CURLE_OUT_OF_MEMORY;
209
9.81k
  DEBUGASSERT(name);
210
211
9.81k
  tqueue = curlx_calloc(1, sizeof(*tqueue));
212
9.81k
  if(!tqueue)
213
0
    goto out;
214
215
9.81k
  Curl_mutex_init(&tqueue->lock);
216
9.81k
  Curl_cond_init(&tqueue->await);
217
9.81k
  Curl_llist_init(&tqueue->sendq, thrdq_item_list_dtor);
218
9.81k
  Curl_llist_init(&tqueue->recvq, thrdq_item_list_dtor);
219
9.81k
  tqueue->fn_free = fn_free;
220
9.81k
  tqueue->fn_process = fn_process;
221
9.81k
  tqueue->fn_event = fn_event;
222
9.81k
  tqueue->fn_user_data = user_data;
223
224
  /* a const string that remains */
225
9.81k
  tqueue->name = name;
226
227
9.81k
  result = Curl_thrdpool_create(&tqueue->tpool, name,
228
9.81k
                                min_threads, max_threads, idle_time_ms,
229
9.81k
                                thrdq_tpool_take,
230
9.81k
                                thrdq_tpool_process,
231
9.81k
                                thrdq_tpool_return,
232
9.81k
                                tqueue);
233
234
9.81k
out:
235
9.81k
  if(result && tqueue) {
236
0
    tqueue->aborted = TRUE;
237
0
    thrdq_unlink(tqueue, FALSE, TRUE);
238
0
    tqueue = NULL;
239
0
  }
240
9.81k
  *ptqueue = tqueue;
241
9.81k
  return result;
242
9.81k
}
243
244
void Curl_thrdq_destroy(struct curl_thrdq *tqueue, bool join)
245
9.81k
{
246
9.81k
  Curl_mutex_acquire(&tqueue->lock);
247
9.81k
  DEBUGASSERT(!tqueue->aborted);
248
9.81k
  tqueue->aborted = TRUE;
249
9.81k
  thrdq_unlink(tqueue, TRUE, join);
250
9.81k
}
251
252
static uint32_t thrdq_get_signals(struct curl_thrdq *tqueue)
253
10.4k
{
254
10.4k
  size_t qlen = Curl_llist_count(&tqueue->sendq);
255
10.4k
  return (qlen <= UINT32_MAX) ? (uint32_t)qlen : UINT32_MAX;
256
10.4k
}
257
258
CURLcode Curl_thrdq_send(struct curl_thrdq *tqueue, void *item,
259
                         const char *description, timediff_t timeout_ms)
260
0
{
261
0
  struct thrdq_item *qitem;
262
0
  CURLcode result = CURLE_OK;
263
0
  uint32_t signals = 0;
264
265
0
  Curl_mutex_acquire(&tqueue->lock);
266
0
  if(tqueue->aborted) {
267
0
    DEBUGASSERT(0);
268
0
    result = CURLE_SEND_ERROR;
269
0
    goto out;
270
0
  }
271
0
  if(timeout_ms < 0) {
272
0
    result = CURLE_OPERATION_TIMEDOUT;
273
0
    goto out;
274
0
  }
275
276
0
  qitem = thrdq_item_create(tqueue, item, description, timeout_ms);
277
0
  if(!qitem) {
278
0
    result = CURLE_OUT_OF_MEMORY;
279
0
    goto out;
280
0
  }
281
0
  Curl_llist_append(&tqueue->sendq, qitem, &qitem->node);
282
0
  signals = thrdq_get_signals(tqueue);
283
284
0
out:
285
0
  Curl_mutex_release(&tqueue->lock);
286
  /* Signal thread pool unlocked to avoid deadlocks. Since we added
287
   * item to the queue already, it might have been taken for processing
288
   * already. Any error in signalling the pool cannot be reported to
289
   * the caller since it needs to give up ownership of item. */
290
0
  if(!result && signals)
291
0
    (void)Curl_thrdpool_signal(tqueue->tpool, (uint32_t)signals);
292
0
  return result;
293
0
}
294
295
bool Curl_thrdq_check_started(struct curl_thrdq *tqueue)
296
0
{
297
0
  size_t unprocessed;
298
299
0
  Curl_mutex_acquire(&tqueue->lock);
300
0
  unprocessed = tqueue->aborted ? 0 : Curl_llist_count(&tqueue->sendq);
301
0
  Curl_mutex_release(&tqueue->lock);
302
0
  return !unprocessed ||
303
0
         !Curl_thrdpool_signal(tqueue->tpool, (uint32_t)unprocessed);
304
0
}
305
306
CURLcode Curl_thrdq_recv(struct curl_thrdq *tqueue, void **pitem)
307
10.4k
{
308
10.4k
  CURLcode result = CURLE_AGAIN;
309
10.4k
  struct Curl_llist_node *e;
310
10.4k
  uint32_t signals = 0;
311
312
10.4k
  *pitem = NULL;
313
10.4k
  Curl_mutex_acquire(&tqueue->lock);
314
10.4k
  if(tqueue->aborted) {
315
0
    DEBUGASSERT(0);
316
0
    result = CURLE_RECV_ERROR;
317
0
    goto out;
318
0
  }
319
320
10.4k
  e = Curl_llist_head(&tqueue->recvq);
321
10.4k
  if(e) {
322
0
    struct thrdq_item *qitem = Curl_node_take_elem(e);
323
0
    *pitem = qitem->item;
324
0
    qitem->item = NULL;
325
0
    thrdq_item_destroy(qitem);
326
0
    result = CURLE_OK;
327
0
  }
328
10.4k
  else
329
10.4k
    signals = thrdq_get_signals(tqueue);
330
331
10.4k
out:
332
10.4k
  Curl_mutex_release(&tqueue->lock);
333
  /* Signal thread pool unlocked to avoid deadlocks. If items await
334
   * processing while nothing was ready, make sure the pool has a
335
   * thread to work on them. An earlier thread start may have failed,
336
   * which `Curl_thrdq_send()` cannot report to its caller. Without
337
   * this, such items would sit unprocessed until the next send. */
338
10.4k
  if(signals)
339
0
    (void)Curl_thrdpool_signal(tqueue->tpool, (uint32_t)signals);
340
10.4k
  return result;
341
10.4k
}
342
343
static void thrdq_llist_clean_matches(struct Curl_llist *llist,
344
                                      Curl_thrdq_item_match_cb *fn_match,
345
                                      void *match_data)
346
0
{
347
0
  struct Curl_llist_node *e, *n;
348
0
  struct thrdq_item *qitem;
349
350
0
  for(e = Curl_llist_head(llist); e; e = n) {
351
0
    n = Curl_node_next(e);
352
0
    qitem = Curl_node_elem(e);
353
0
    if(fn_match(qitem->item, match_data))
354
0
      Curl_node_remove(e);
355
0
  }
356
0
}
357
358
void Curl_thrdq_clear(struct curl_thrdq *tqueue,
359
                      Curl_thrdq_item_match_cb *fn_match,
360
                      void *match_data)
361
0
{
362
0
  Curl_mutex_acquire(&tqueue->lock);
363
0
  if(tqueue->aborted) {
364
0
    DEBUGASSERT(0);
365
0
    goto out;
366
0
  }
367
0
  thrdq_llist_clean_matches(&tqueue->sendq, fn_match, match_data);
368
0
  thrdq_llist_clean_matches(&tqueue->recvq, fn_match, match_data);
369
0
out:
370
0
  Curl_mutex_release(&tqueue->lock);
371
0
}
372
373
#ifdef UNITTESTS
374
/* @unittest 3301 */
375
UNITTEST CURLcode thrdq_await_done(struct curl_thrdq *tqueue,
376
                                   uint32_t timeout_ms);
377
UNITTEST CURLcode thrdq_await_done(struct curl_thrdq *tqueue,
378
                                   uint32_t timeout_ms)
379
0
{
380
0
  return Curl_thrdpool_await_idle(tqueue->tpool, timeout_ms);
381
0
}
382
#endif
383
384
CURLcode Curl_thrdq_set_props(struct curl_thrdq *tqueue,
385
                              uint32_t min_threads,
386
                              uint32_t max_threads,
387
                              uint32_t idle_time_ms)
388
0
{
389
0
  CURLcode result;
390
0
  uint32_t signals;
391
392
0
  Curl_mutex_acquire(&tqueue->lock);
393
0
  signals = thrdq_get_signals(tqueue);
394
0
  Curl_mutex_release(&tqueue->lock);
395
396
0
  result = Curl_thrdpool_set_props(tqueue->tpool, min_threads,
397
0
                                   max_threads, idle_time_ms);
398
0
  if(!result && signals)
399
0
    result = Curl_thrdpool_signal(tqueue->tpool, (uint32_t)signals);
400
0
  return result;
401
0
}
402
403
#ifdef CURLVERBOSE
404
void Curl_thrdq_trace(struct curl_thrdq *tqueue,
405
                      struct Curl_easy *data)
406
20.2k
{
407
20.2k
  struct curl_trc_feat *feat = &Curl_trc_feat_threads;
408
20.2k
  if(Curl_trc_ft_is_verbose(data, feat)) {
409
0
    struct Curl_llist_node *e;
410
0
    struct thrdq_item *qitem;
411
412
0
    Curl_thrdpool_trace(tqueue->tpool, data);
413
0
    Curl_mutex_acquire(&tqueue->lock);
414
0
    if(!Curl_llist_count(&tqueue->sendq) &&
415
0
       !Curl_llist_count(&tqueue->recvq)) {
416
0
      Curl_trc_feat_infof(data, feat, "[TQUEUE-%s] empty", tqueue->name);
417
0
    }
418
0
    for(e = Curl_llist_head(&tqueue->sendq); e; e = Curl_node_next(e)) {
419
0
      qitem = Curl_node_elem(e);
420
0
      Curl_trc_feat_infof(data, feat, "[TQUEUE-%s] in: %s",
421
0
                          tqueue->name, qitem->description);
422
0
    }
423
0
    for(e = Curl_llist_head(&tqueue->recvq); e; e = Curl_node_next(e)) {
424
0
      qitem = Curl_node_elem(e);
425
0
      Curl_trc_feat_infof(data, feat, "[TQUEUE-%s] out: %s",
426
0
                          tqueue->name, qitem->description);
427
0
    }
428
0
    Curl_mutex_release(&tqueue->lock);
429
0
  }
430
20.2k
}
431
#endif
432
433
#endif /* USE_THREADS */