Coverage Report

Created: 2026-11-12 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/htslib/thread_pool.c
Line
Count
Source
1
/*  thread_pool.c -- A pool of generic worker threads
2
3
    Copyright (c) 2013-2020, 2025 Genome Research Ltd.
4
5
    Author: James Bonfield <jkb@sanger.ac.uk>
6
7
Permission is hereby granted, free of charge, to any person obtaining a copy
8
of this software and associated documentation files (the "Software"), to deal
9
in the Software without restriction, including without limitation the rights
10
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
copies of the Software, and to permit persons to whom the Software is
12
furnished to do so, subject to the following conditions:
13
14
The above copyright notice and this permission notice shall be included in
15
all copies or substantial portions of the Software.
16
17
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23
DEALINGS IN THE SOFTWARE.  */
24
25
#ifndef TEST_MAIN
26
#define HTS_BUILDING_LIBRARY // Enables HTSLIB_EXPORT, see htslib/hts_defs.h
27
#include <config.h>
28
#endif
29
30
#include <stdlib.h>
31
#include <inttypes.h>
32
#include <signal.h>
33
#include <errno.h>
34
#include <stdio.h>
35
#include <string.h>
36
#include <sys/time.h>
37
#include <assert.h>
38
#include <stdarg.h>
39
#include <unistd.h>
40
#include <limits.h>
41
42
#include "thread_pool_internal.h"
43
#include "htslib/hts_alloc.h"
44
#include "htslib/hts_log.h"
45
46
// Minimum stack size for threads.  Required for some rANS codecs
47
// that use over 2Mbytes of stack for encoder / decoder state
48
0
#define HTS_MIN_THREAD_STACK (3 * 1024 * 1024)
49
50
static void hts_tpool_process_detach_locked(hts_tpool *p,
51
                                            hts_tpool_process *q);
52
53
//#define DEBUG
54
55
// Return the worker ID index, from 0 to nthreads-1.
56
// Return <0 on error, but this shouldn't be possible
57
0
int hts_tpool_worker_id(hts_tpool *p) {
58
0
    if (!p)
59
0
        return -1;
60
0
    pthread_t s = pthread_self();
61
0
    int i;
62
0
    for (i = 0; i < p->tsize; i++) {
63
0
        if (pthread_equal(s, p->t[i].tid))
64
0
            return i;
65
0
    }
66
0
    return -1;
67
0
}
68
69
#ifdef DEBUG
70
void DBG_OUT(FILE *fp, char *fmt, ...) {
71
    va_list args;
72
    va_start(args, fmt);
73
    vfprintf(fp, fmt, args);
74
    va_end(args);
75
}
76
#else
77
0
#define DBG_OUT(...) do{}while(0)
78
#endif
79
80
/* ----------------------------------------------------------------------------
81
 * A process-queue to hold results from the thread pool.
82
 *
83
 * Each thread pool may have jobs of multiple types being queued up and
84
 * interleaved, so we attach several job process-queues to a single pool.
85
 *
86
 * The jobs themselves are expected to push their results onto their
87
 * appropriate results queue.
88
 */
89
90
/*
91
 * Adds a result to the end of the process result queue.
92
 *
93
 * Returns 0 on success;
94
 *        -1 on failure
95
 */
96
0
static int hts_tpool_add_result(hts_tpool_job *j, void *data) {
97
0
    hts_tpool_process *q = j->q;
98
0
    hts_tpool_result *r;
99
100
0
    pthread_mutex_lock(&q->p->pool_m);
101
102
0
    DBG_OUT(stderr, "%d: Adding result to queue %p, serial %"PRId64", %d of %d\n",
103
0
            hts_tpool_worker_id(j->p), q, j->serial, q->n_output+1, q->qsize);
104
105
0
    if (--q->n_processing == 0)
106
0
        pthread_cond_signal(&q->none_processing_c);
107
108
    /* No results queue is fine if we don't want any results back */
109
0
    if (q->in_only) {
110
0
        pthread_mutex_unlock(&q->p->pool_m);
111
0
        return 0;
112
0
    }
113
114
0
    if (!(r = malloc(sizeof(*r)))) {
115
0
        pthread_mutex_unlock(&q->p->pool_m);
116
0
        hts_tpool_process_shutdown(q);
117
0
        return -1;
118
0
    }
119
120
0
    r->next = NULL;
121
0
    r->data = data;
122
0
    r->result_cleanup = j->result_cleanup;
123
0
    r->serial = j->serial;
124
125
0
    q->n_output++;
126
0
    if (q->output_tail) {
127
0
        q->output_tail->next = r;
128
0
        q->output_tail = r;
129
0
    } else {
130
0
        q->output_head = q->output_tail = r;
131
0
    }
132
133
0
    assert(r->serial >= q->next_serial    // Or it will never be dequeued ...
134
0
           || q->next_serial == INT_MAX); // ... unless flush in progress.
135
0
    if (r->serial == q->next_serial) {
136
0
        DBG_OUT(stderr, "%d: Broadcasting result_avail (id %"PRId64")\n",
137
0
                hts_tpool_worker_id(j->p), r->serial);
138
0
        pthread_cond_broadcast(&q->output_avail_c);
139
0
        DBG_OUT(stderr, "%d: Broadcast complete\n", hts_tpool_worker_id(j->p));
140
0
    }
141
142
0
    pthread_mutex_unlock(&q->p->pool_m);
143
144
0
    return 0;
145
0
}
146
147
static void wake_next_worker(hts_tpool_process *q, int locked);
148
149
/* Core of hts_tpool_next_result() */
150
0
static hts_tpool_result *hts_tpool_next_result_locked(hts_tpool_process *q) {
151
0
    hts_tpool_result *r, *last;
152
153
0
    if (q->shutdown)
154
0
        return NULL;
155
156
0
    for (last = NULL, r = q->output_head; r; last = r, r = r->next) {
157
0
        if (r->serial == q->next_serial)
158
0
            break;
159
0
    }
160
161
0
    if (r) {
162
        // Remove r from out linked list
163
0
        if (q->output_head == r)
164
0
            q->output_head = r->next;
165
0
        else
166
0
            last->next = r->next;
167
168
0
        if (q->output_tail == r)
169
0
            q->output_tail = last;
170
171
0
        if (!q->output_head)
172
0
            q->output_tail = NULL;
173
174
0
        q->next_serial++;
175
0
        q->n_output--;
176
177
0
        if (q->qsize && q->n_output < q->qsize) {
178
            // Not technically input full, but can guarantee there is
179
            // room for the input to go somewhere so we still signal.
180
            // The waiting code will then check the condition again.
181
0
            if (q->n_input < q->qsize)
182
0
                pthread_cond_signal(&q->input_not_full_c);
183
0
            if (!q->shutdown)
184
0
                wake_next_worker(q, 1);
185
0
        }
186
0
    }
187
188
0
    return r;
189
0
}
190
191
/*
192
 * Pulls the next item off the process result queue.  The caller should free
193
 * it (and any internals as appropriate) after use.  This doesn't wait for a
194
 * result to be present.
195
 *
196
 * Results will be returned in strict order.
197
 *
198
 * Returns hts_tpool_result pointer if a result is ready.
199
 *         NULL if not.
200
 */
201
0
hts_tpool_result *hts_tpool_next_result(hts_tpool_process *q) {
202
0
    hts_tpool_result *r;
203
204
0
    DBG_OUT(stderr, "Requesting next result on queue %p\n", q);
205
206
0
    pthread_mutex_lock(&q->p->pool_m);
207
0
    r = hts_tpool_next_result_locked(q);
208
0
    pthread_mutex_unlock(&q->p->pool_m);
209
210
0
    DBG_OUT(stderr, "(q=%p) Found %p\n", q, r);
211
212
0
    return r;
213
0
}
214
215
/*
216
 * Pulls the next item off the process result queue.  The caller should free
217
 * it (and any internals as appropriate) after use.  This will wait for
218
 * a result to be present if none are currently available.
219
 *
220
 * Results will be returned in strict order.
221
 *
222
 * Returns hts_tpool_result pointer if a result is ready.
223
 *         NULL on error or during shutdown.
224
 */
225
0
hts_tpool_result *hts_tpool_next_result_wait(hts_tpool_process *q) {
226
0
    hts_tpool_result *r;
227
228
0
    pthread_mutex_lock(&q->p->pool_m);
229
0
    while (!(r = hts_tpool_next_result_locked(q))) {
230
        /* Possible race here now avoided via _locked() call, but in case... */
231
0
        struct timeval now;
232
0
        struct timespec timeout;
233
234
0
        gettimeofday(&now, NULL);
235
0
        timeout.tv_sec = now.tv_sec + 10;
236
0
        timeout.tv_nsec = now.tv_usec * 1000;
237
238
0
        q->ref_count++;
239
0
        if (q->shutdown) {
240
0
            int rc = --q->ref_count;
241
0
            pthread_mutex_unlock(&q->p->pool_m);
242
0
            if (rc == 0)
243
0
                hts_tpool_process_destroy(q);
244
0
            return NULL;
245
0
        }
246
0
        pthread_cond_timedwait(&q->output_avail_c, &q->p->pool_m, &timeout);
247
248
0
        q->ref_count--;
249
0
    }
250
0
    pthread_mutex_unlock(&q->p->pool_m);
251
252
0
    return r;
253
0
}
254
255
/*
256
 * Returns true if there are no items in the process results queue and
257
 * also none still pending.
258
 */
259
0
int hts_tpool_process_empty(hts_tpool_process *q) {
260
0
    int empty;
261
262
0
    pthread_mutex_lock(&q->p->pool_m);
263
0
    empty = q->n_input == 0 && q->n_processing == 0 && q->n_output == 0;
264
0
    pthread_mutex_unlock(&q->p->pool_m);
265
266
0
    return empty;
267
0
}
268
269
0
void hts_tpool_process_ref_incr(hts_tpool_process *q) {
270
0
    pthread_mutex_lock(&q->p->pool_m);
271
0
    q->ref_count++;
272
0
    pthread_mutex_unlock(&q->p->pool_m);
273
0
}
274
275
0
void hts_tpool_process_ref_decr(hts_tpool_process *q) {
276
0
    pthread_mutex_lock(&q->p->pool_m);
277
0
    if (--q->ref_count <= 0) {
278
0
        pthread_mutex_unlock(&q->p->pool_m);
279
0
        hts_tpool_process_destroy(q);
280
0
        return;
281
0
    }
282
283
    // maybe also call destroy here if needed?
284
0
    pthread_mutex_unlock(&q->p->pool_m);
285
0
}
286
287
/*
288
 * Returns the number of completed jobs in the process results queue.
289
 */
290
0
int hts_tpool_process_len(hts_tpool_process *q) {
291
0
    int len;
292
293
0
    pthread_mutex_lock(&q->p->pool_m);
294
0
    len = q->n_output;
295
0
    pthread_mutex_unlock(&q->p->pool_m);
296
297
0
    return len;
298
0
}
299
300
/*
301
 * Returns the number of completed jobs in the process results queue plus the
302
 * number running and queued up to run.
303
 */
304
0
int hts_tpool_process_sz(hts_tpool_process *q) {
305
0
    int len;
306
307
0
    pthread_mutex_lock(&q->p->pool_m);
308
0
    len = q->n_output + q->n_input + q->n_processing;
309
0
    pthread_mutex_unlock(&q->p->pool_m);
310
311
0
    return len;
312
0
}
313
314
/*
315
 * Shutdown a process.
316
 *
317
 * This sets the shutdown flag and wakes any threads waiting on process
318
 * condition variables.
319
 */
320
0
static void hts_tpool_process_shutdown_locked(hts_tpool_process *q) {
321
0
    q->shutdown = 1;
322
0
    pthread_cond_broadcast(&q->output_avail_c);
323
0
    pthread_cond_broadcast(&q->input_not_full_c);
324
0
    pthread_cond_broadcast(&q->input_empty_c);
325
0
    pthread_cond_broadcast(&q->none_processing_c);
326
0
}
327
328
0
void hts_tpool_process_shutdown(hts_tpool_process *q) {
329
0
    pthread_mutex_lock(&q->p->pool_m);
330
0
    hts_tpool_process_shutdown_locked(q);
331
0
    pthread_mutex_unlock(&q->p->pool_m);
332
0
}
333
334
0
int hts_tpool_process_is_shutdown(hts_tpool_process *q) {
335
0
    pthread_mutex_lock(&q->p->pool_m);
336
0
    int r = q->shutdown;
337
0
    pthread_mutex_unlock(&q->p->pool_m);
338
0
    return r;
339
0
}
340
341
/*
342
 * Frees a result 'r' and if free_data is true also frees
343
 * the internal r->data result too.
344
 */
345
0
void hts_tpool_delete_result(hts_tpool_result *r, int free_data) {
346
0
    if (!r)
347
0
        return;
348
349
0
    if (free_data && r->data)
350
0
        free(r->data);
351
352
0
    free(r);
353
0
}
354
355
/*
356
 * Returns the data portion of a hts_tpool_result, corresponding
357
 * to the actual "result" itself.
358
 */
359
0
void *hts_tpool_result_data(hts_tpool_result *r) {
360
0
    return r->data;
361
0
}
362
363
/*
364
 * Initialises a thread process-queue.
365
 *
366
 * In_only, if true, indicates that the process generates does not need to
367
 * hold any output.  Otherwise an output queue is used to store the results
368
 * of processing each input job.
369
 *
370
 * Results hts_tpool_process pointer on success;
371
 *         NULL on failure
372
 */
373
0
hts_tpool_process *hts_tpool_process_init(hts_tpool *p, int qsize, int in_only) {
374
0
    hts_tpool_process *q = malloc(sizeof(*q));
375
0
    if (!q)
376
0
        return NULL;
377
378
0
    pthread_cond_init(&q->output_avail_c,   NULL);
379
0
    pthread_cond_init(&q->input_not_full_c, NULL);
380
0
    pthread_cond_init(&q->input_empty_c,    NULL);
381
0
    pthread_cond_init(&q->none_processing_c,NULL);
382
383
0
    q->p           = p;
384
0
    q->input_head  = NULL;
385
0
    q->input_tail  = NULL;
386
0
    q->output_head = NULL;
387
0
    q->output_tail = NULL;
388
0
    q->next_serial = 0;
389
0
    q->curr_serial = 0;
390
0
    q->no_more_input = 0;
391
0
    q->n_input     = 0;
392
0
    q->n_output    = 0;
393
0
    q->n_processing= 0;
394
0
    q->qsize       = qsize;
395
0
    q->in_only     = in_only;
396
0
    q->shutdown    = 0;
397
0
    q->wake_dispatch = 0;
398
0
    q->ref_count   = 1;
399
400
0
    q->next        = NULL;
401
0
    q->prev        = NULL;
402
403
0
    hts_tpool_process_attach(p, q);
404
405
0
    return q;
406
0
}
407
408
/* Deallocates memory for a thread process-queue.
409
 * Must be called before the thread pool is destroyed.
410
 */
411
0
void hts_tpool_process_destroy(hts_tpool_process *q) {
412
0
    DBG_OUT(stderr, "Destroying results queue %p\n", q);
413
414
0
    if (!q)
415
0
        return;
416
417
    // Prevent dispatch from queuing up any more jobs.
418
    // We want to reset (and flush) the queue here, before
419
    // we set the shutdown flag, but we need to avoid races
420
    // with queue more input during reset.
421
0
    pthread_mutex_lock(&q->p->pool_m);
422
0
    q->no_more_input = 1;
423
0
    pthread_mutex_unlock(&q->p->pool_m);
424
425
    // Ensure it's fully drained before destroying the queue
426
0
    hts_tpool_process_reset(q, 0);
427
0
    pthread_mutex_lock(&q->p->pool_m);
428
0
    hts_tpool_process_detach_locked(q->p, q);
429
0
    hts_tpool_process_shutdown_locked(q);
430
431
    // Maybe a worker is scanning this queue, so delay destruction
432
0
    if (--q->ref_count > 0) {
433
0
        pthread_mutex_unlock(&q->p->pool_m);
434
0
        return;
435
0
    }
436
437
0
    pthread_cond_destroy(&q->output_avail_c);
438
0
    pthread_cond_destroy(&q->input_not_full_c);
439
0
    pthread_cond_destroy(&q->input_empty_c);
440
0
    pthread_cond_destroy(&q->none_processing_c);
441
0
    pthread_mutex_unlock(&q->p->pool_m);
442
443
0
    free(q);
444
445
0
    DBG_OUT(stderr, "Destroyed results queue %p\n", q);
446
0
}
447
448
449
/*
450
 * Attach and detach a thread process-queue with / from the thread pool
451
 * scheduler.
452
 *
453
 * We need to do attach after making a thread process, but may also wish
454
 * to temporarily detach if we wish to stop running jobs on a specific
455
 * process while permitting other process to continue.
456
 */
457
0
void hts_tpool_process_attach(hts_tpool *p, hts_tpool_process *q) {
458
0
    pthread_mutex_lock(&p->pool_m);
459
0
    if (p->q_head) {
460
0
        q->next = p->q_head;
461
0
        q->prev = p->q_head->prev;
462
0
        p->q_head->prev->next = q;
463
0
        p->q_head->prev = q;
464
0
    } else {
465
0
        q->next = q;
466
0
        q->prev = q;
467
0
    }
468
0
    p->q_head = q;
469
0
    assert(p->q_head && p->q_head->prev && p->q_head->next);
470
0
    pthread_mutex_unlock(&p->pool_m);
471
0
}
472
473
static void hts_tpool_process_detach_locked(hts_tpool *p,
474
0
                                            hts_tpool_process *q) {
475
0
    if (!p->q_head || !q->prev || !q->next)
476
0
        return;
477
478
0
    hts_tpool_process *curr = p->q_head, *first = curr;
479
0
    do {
480
0
        if (curr == q) {
481
0
            q->next->prev = q->prev;
482
0
            q->prev->next = q->next;
483
0
            p->q_head = q->next;
484
0
            q->next = q->prev = NULL;
485
486
            // Last one
487
0
            if (p->q_head == q)
488
0
                p->q_head = NULL;
489
0
            break;
490
0
        }
491
492
0
        curr = curr->next;
493
0
    } while (curr != first);
494
0
}
495
496
0
void hts_tpool_process_detach(hts_tpool *p, hts_tpool_process *q) {
497
0
    pthread_mutex_lock(&p->pool_m);
498
0
    hts_tpool_process_detach_locked(p, q);
499
0
    pthread_mutex_unlock(&p->pool_m);
500
0
}
501
502
503
/* ----------------------------------------------------------------------------
504
 * The thread pool.
505
 */
506
507
#define TDIFF(t2,t1) ((t2.tv_sec-t1.tv_sec)*1000000 + t2.tv_usec-t1.tv_usec)
508
509
/*
510
 * A worker thread.
511
 *
512
 * Once woken, each thread checks each process-queue in the pool in turn,
513
 * looking for input jobs that also have room for the output (if it requires
514
 * storing).  If found, we execute it and repeat.
515
 *
516
 * If we checked all input queues and find no such job, then we wait until we
517
 * are signalled to check again.
518
 */
519
0
static void *tpool_worker(void *arg) {
520
0
    hts_tpool_worker *w = (hts_tpool_worker *)arg;
521
0
    hts_tpool *p = w->p;
522
0
    hts_tpool_job *j;
523
524
0
    pthread_mutex_lock(&p->pool_m);
525
0
    while (!p->shutdown) {
526
        // Pop an item off the pool queue
527
528
0
        assert(p->q_head == 0 || (p->q_head->prev && p->q_head->next));
529
530
0
        int work_to_do = 0;
531
0
        hts_tpool_process *first = p->q_head, *q = first;
532
0
        do {
533
            // Iterate over queues, finding one with jobs and also
534
            // room to put the result.
535
            //if (q && q->input_head && !hts_tpool_process_output_full(q)) {
536
0
            if (q && q->input_head
537
0
                && q->qsize - q->n_output > q->n_processing
538
0
                && !q->shutdown) {
539
0
                work_to_do = 1;
540
0
                break;
541
0
            }
542
543
0
            if (q) q = q->next;
544
0
        } while (q && q != first);
545
546
0
        if (!work_to_do) {
547
            // We scanned all queues and cannot process any, so we wait.
548
0
            p->nwaiting++;
549
550
            // Push this thread to the top of the waiting stack
551
0
            if (p->t_stack_top == -1 || p->t_stack_top > w->idx)
552
0
                p->t_stack_top = w->idx;
553
554
0
            p->t_stack[w->idx] = 1;
555
//            printf("%2d: no work.  In=%d Proc=%d Out=%d  full=%d\n",
556
//                   w->idx, p->q_head->n_input, p->q_head->n_processing, p->q_head->n_output,
557
//                   hts_tpool_process_output_full(p->q_head));
558
0
            pthread_cond_wait(&w->pending_c, &p->pool_m);
559
0
            p->t_stack[w->idx] = 0;
560
561
            /* Find new t_stack_top */
562
0
            int i;
563
0
            p->t_stack_top = -1;
564
0
            for (i = 0; i < p->tsize; i++) {
565
0
                if (p->t_stack[i]) {
566
0
                    p->t_stack_top = i;
567
0
                    break;
568
0
                }
569
0
            }
570
571
0
            p->nwaiting--;
572
0
            continue; // To outer loop.
573
0
        }
574
575
        // Otherwise work_to_do, so process as many items in this queue as
576
        // possible before switching to another queue.  This means threads
577
        // often end up being dedicated to one type of work.
578
0
        q->ref_count++;
579
0
        while (q->input_head && q->qsize - q->n_output > q->n_processing) {
580
0
            if (p->shutdown)
581
0
                goto shutdown;
582
583
0
            if (q->shutdown)
584
                // Queue shutdown, but there may be other queues
585
0
                break;
586
587
0
            j = q->input_head;
588
0
            assert(j->p == p);
589
590
0
            if (!(q->input_head = j->next))
591
0
                q->input_tail = NULL;
592
593
            // Transitioning from full queue to not-full means we can wake up
594
            // any blocked dispatch threads.  We broadcast this as it's only
595
            // happening once (on the transition) rather than every time we
596
            // are below qsize.
597
            // (I wish I could remember why io_lib rev 3660 changed this from
598
            //  == to >=, but keeping it just in case!)
599
0
            q->n_processing++;
600
0
            if (q->n_input-- >= q->qsize)
601
0
                pthread_cond_broadcast(&q->input_not_full_c);
602
603
0
            if (q->n_input == 0)
604
0
                pthread_cond_signal(&q->input_empty_c);
605
606
0
            p->njobs--; // Total number of jobs; used to adjust to CPU scaling
607
608
0
            pthread_mutex_unlock(&p->pool_m);
609
610
0
            DBG_OUT(stderr, "%d: Processing queue %p, serial %"PRId64"\n",
611
0
                    hts_tpool_worker_id(j->p), q, j->serial);
612
613
0
            if (hts_tpool_add_result(j, j->func(j->arg)) < 0)
614
0
                goto err;
615
            //memset(j, 0xbb, sizeof(*j));
616
0
            free(j);
617
618
0
            pthread_mutex_lock(&p->pool_m);
619
0
        }
620
0
        if (--q->ref_count == 0) { // we were the last user
621
0
            hts_tpool_process_destroy(q);
622
0
        } else {
623
            // Out of jobs on this queue, so restart search from next one.
624
            // This is equivalent to "work-stealing".
625
0
            if (p->q_head)
626
0
                p->q_head = p->q_head->next;
627
0
        }
628
0
    }
629
630
0
 shutdown:
631
0
    pthread_mutex_unlock(&p->pool_m);
632
#ifdef DEBUG
633
    fprintf(stderr, "%d: Shutting down\n", hts_tpool_worker_id(p));
634
#endif
635
0
    return NULL;
636
637
0
 err:
638
#ifdef DEBUG
639
    fprintf(stderr, "%d: Failed to add result\n", hts_tpool_worker_id(p));
640
#endif
641
    // Hard failure, so shutdown all queues
642
0
    pthread_mutex_lock(&p->pool_m);
643
0
    hts_tpool_process *first = p->q_head, *q = first;
644
0
    if (q) {
645
0
        do {
646
0
            hts_tpool_process_shutdown_locked(q);
647
0
            q->shutdown = 2; // signify error.
648
0
            q = q->next;
649
0
        } while (q != first);
650
0
    }
651
0
    pthread_mutex_unlock(&p->pool_m);
652
0
    return NULL;
653
0
}
654
655
0
static void wake_next_worker(hts_tpool_process *q, int locked) {
656
0
    if (!q) return;
657
0
    hts_tpool *p = q->p;
658
0
    if (!locked)
659
0
        pthread_mutex_lock(&p->pool_m);
660
661
    // Update the q_head to be this queue so we'll start processing
662
    // the queue we know to have results.
663
0
    assert(q->prev && q->next); // attached
664
0
    p->q_head = q;
665
666
    // Wake up if we have more jobs waiting than CPUs. This partially combats
667
    // CPU frequency scaling effects.  Starting too many threads and then
668
    // running out of jobs can cause each thread to have lots of start/stop
669
    // cycles, which then translates often to CPU frequency scaling
670
    // adjustments.  Instead it is better to only start as many threads as we
671
    // need to keep the throughput up, meaning some threads run flat out and
672
    // others are idle.
673
    //
674
    // This isn't perfect as we need to know how many can actually start,
675
    // rather than how many are waiting.  A limit on output queue size makes
676
    // these two figures different.
677
0
    assert(p->njobs >= q->n_input);
678
679
0
    int running = p->tsize - p->nwaiting;
680
0
    int sig = p->t_stack_top >= 0 && p->njobs > p->tsize - p->nwaiting
681
0
        && (q->n_processing < q->qsize - q->n_output);
682
683
//#define AVG_USAGE
684
#ifdef AVG_USAGE
685
    // Track average number of running threads and try to keep close.
686
    // We permit this to change, but slowly.  This avoids "boom and bust" cycles
687
    // where we read a lot of data, start a lot of jobs, then become idle again.
688
    // This way some threads run steadily and others dormant, which is better
689
    // for throughput.
690
    //
691
    // It's 50:50 if this is a good thing.  It helps some tasks quite significantly
692
    // while slightly hindering other (perhaps more usual) jobs.
693
694
    if (++p->n_count == 256) {
695
        p->n_count >>= 1;
696
        p->n_running >>= 1;
697
    }
698
    p->n_running += running;
699
    // Built in lag to avoid see-sawing.  Is this safe in all cases?
700
    if (sig && p->n_count >= 128 && running*p->n_count > p->n_running+1) sig=0;
701
#endif
702
703
0
    if (0) {
704
0
        printf("%d waiting, %d running, %d output, %d, arun %d => %d\t", p->njobs,
705
0
               running, q->n_output, q->qsize - q->n_output,
706
0
               p->n_running/p->n_count, sig);
707
0
        int i;
708
0
        for (i = 0; i < p->tsize; i++)
709
0
            putchar("x "[p->t_stack[i]]);
710
0
        putchar('\n');
711
0
    }
712
713
0
    if (sig)
714
0
        pthread_cond_signal(&p->t[p->t_stack_top].pending_c);
715
716
0
    if (!locked)
717
0
        pthread_mutex_unlock(&p->pool_m);
718
0
}
719
720
/*
721
 * Creates a worker pool with n worker threads.
722
 *
723
 * Returns pool pointer on success;
724
 *         NULL on failure
725
 */
726
0
hts_tpool *hts_tpool_init(int n) {
727
0
    int t_idx = 0;
728
0
    size_t stack_size = 0;
729
0
    pthread_attr_t pattr;
730
0
    int pattr_init_done = 0;
731
0
    hts_tpool *p = malloc(sizeof(*p));
732
0
    if (!p)
733
0
        return NULL;
734
0
    p->tsize = n;
735
0
    p->njobs = 0;
736
0
    p->nwaiting = 0;
737
0
    p->shutdown = 0;
738
0
    p->q_head = NULL;
739
0
    p->t_stack = NULL;
740
0
    p->n_count = 0;
741
0
    p->n_running = 0;
742
0
    p->t = hts_malloc_p(sizeof(p->t[0]), n);
743
0
    if (!p->t) {
744
0
        free(p);
745
0
        return NULL;
746
0
    }
747
0
    p->t_stack = hts_malloc_p(sizeof(*p->t_stack), n);
748
0
    if (!p->t_stack) {
749
0
        free(p->t);
750
0
        free(p);
751
0
        return NULL;
752
0
    }
753
0
    p->t_stack_top = -1;
754
755
0
    pthread_mutexattr_t attr;
756
0
    pthread_mutexattr_init(&attr);
757
0
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
758
0
    pthread_mutex_init(&p->pool_m, &attr);
759
0
    pthread_mutexattr_destroy(&attr);
760
761
0
    pthread_mutex_lock(&p->pool_m);
762
763
    // Ensure new threads have a reasonably large stack.  On some platforms,
764
    // for example MacOS which defaults to 512Kb, this is not big enough
765
    // for some of the rANS codecs.
766
767
0
    if (pthread_attr_init(&pattr) < 0)
768
0
        goto cleanup;
769
0
    pattr_init_done = 1;
770
0
    if (pthread_attr_getstacksize(&pattr, &stack_size) < 0)
771
0
        goto cleanup;
772
0
    if (stack_size < HTS_MIN_THREAD_STACK) {
773
0
        if (pthread_attr_setstacksize(&pattr, HTS_MIN_THREAD_STACK) < 0)
774
0
            goto cleanup;
775
0
    }
776
777
0
    for (t_idx = 0; t_idx < n; t_idx++) {
778
0
        hts_tpool_worker *w = &p->t[t_idx];
779
0
        p->t_stack[t_idx] = 0;
780
0
        w->p = p;
781
0
        w->idx = t_idx;
782
0
        pthread_cond_init(&w->pending_c, NULL);
783
0
        if (0 != pthread_create(&w->tid, &pattr, tpool_worker, w))
784
0
            goto cleanup;
785
0
    }
786
787
0
    pthread_mutex_unlock(&p->pool_m);
788
0
    pthread_attr_destroy(&pattr);
789
790
0
    return p;
791
792
0
 cleanup: {
793
        // Any threads started will be waiting for p->pool_m, so we can
794
        // stop them cleanly by setting p->shutdown, releasing the mutex and
795
        // waiting for them to finish.
796
0
        int j;
797
0
        int save_errno = errno;
798
0
        hts_log_error("Couldn't start thread pool worker : %s",
799
0
                      strerror(errno));
800
0
        p->shutdown = 1;
801
0
        pthread_mutex_unlock(&p->pool_m);
802
0
        for (j = 0; j < t_idx; j++) {
803
0
            pthread_join(p->t[j].tid, NULL);
804
0
            pthread_cond_destroy(&p->t[j].pending_c);
805
0
        }
806
0
        pthread_mutex_destroy(&p->pool_m);
807
0
        if (pattr_init_done)
808
0
            pthread_attr_destroy(&pattr);
809
0
        free(p->t_stack);
810
0
        free(p->t);
811
0
        free(p);
812
0
        errno = save_errno;
813
0
        return NULL;
814
0
    }
815
0
}
816
817
/*
818
 * Returns the number of requested threads for a pool.
819
 */
820
0
int hts_tpool_size(hts_tpool *p) {
821
0
    return p->tsize;
822
0
}
823
824
/*
825
 * Adds an item to the work pool.
826
 *
827
 * Returns 0 on success
828
 *        -1 on failure
829
 */
830
int hts_tpool_dispatch(hts_tpool *p, hts_tpool_process *q,
831
0
                    void *(*func)(void *arg), void *arg) {
832
0
    return hts_tpool_dispatch3(p, q, func, arg, NULL, NULL, 0);
833
0
}
834
835
/*
836
 * As above but optional non-block flag.
837
 *
838
 * nonblock  0 => block if input queue is full
839
 * nonblock +1 => don't block if input queue is full, but do not add task
840
 * nonblock -1 => add task regardless of whether queue is full (over-size)
841
 */
842
int hts_tpool_dispatch2(hts_tpool *p, hts_tpool_process *q,
843
0
                        void *(*func)(void *arg), void *arg, int nonblock) {
844
0
    return hts_tpool_dispatch3(p, q, func, arg, NULL, NULL, nonblock);
845
0
}
846
847
int hts_tpool_dispatch3(hts_tpool *p, hts_tpool_process *q,
848
                        void *(*exec_func)(void *arg), void *arg,
849
                        void (*job_cleanup)(void *arg),
850
                        void (*result_cleanup)(void *data),
851
0
                        int nonblock) {
852
0
    hts_tpool_job *j;
853
854
0
    pthread_mutex_lock(&p->pool_m);
855
856
0
    DBG_OUT(stderr, "Dispatching job for queue %p, serial %"PRId64"\n",
857
0
            q, q->curr_serial);
858
859
0
    if ((q->no_more_input || q->n_input >= q->qsize) && nonblock == 1) {
860
0
        pthread_mutex_unlock(&p->pool_m);
861
0
        errno = EAGAIN;
862
0
        return -1;
863
0
    }
864
865
0
    if (!(j = malloc(sizeof(*j)))) {
866
0
        pthread_mutex_unlock(&p->pool_m);
867
0
        return -1;
868
0
    }
869
0
    j->func = exec_func;
870
0
    j->arg = arg;
871
0
    j->job_cleanup = job_cleanup;
872
0
    j->result_cleanup = result_cleanup;
873
0
    j->next = NULL;
874
0
    j->p = p;
875
0
    j->q = q;
876
0
    j->serial = q->curr_serial++;
877
878
0
    if (nonblock == 0) {
879
0
        while ((q->no_more_input || q->n_input >= q->qsize) &&
880
0
               !q->shutdown && !q->wake_dispatch) {
881
0
            pthread_cond_wait(&q->input_not_full_c, &q->p->pool_m);
882
0
        }
883
0
        if (q->no_more_input || q->shutdown) {
884
0
            free(j);
885
0
            pthread_mutex_unlock(&p->pool_m);
886
0
            return -1;
887
0
        }
888
0
        if (q->wake_dispatch) {
889
            //fprintf(stderr, "Wake => non-block for this operation\n");
890
0
            q->wake_dispatch = 0;
891
0
        }
892
0
    }
893
894
0
    p->njobs++;    // total across all queues
895
0
    q->n_input++;  // queue specific
896
897
0
    if (q->input_tail) {
898
0
        q->input_tail->next = j;
899
0
        q->input_tail = j;
900
0
    } else {
901
0
        q->input_head = q->input_tail = j;
902
0
    }
903
904
0
    DBG_OUT(stderr, "Dispatched (serial %"PRId64")\n", j->serial);
905
906
    // Let a worker know we have data.
907
    // Keep incoming queue at 1 per running thread, so there is always
908
    // something waiting when they end their current task.  If we go above
909
    // this signal to start more threads (if available). This has the effect
910
    // of concentrating jobs to fewer cores when we are I/O bound, which in
911
    // turn benefits systems with auto CPU frequency scaling.
912
0
    if (!q->shutdown)
913
0
        wake_next_worker(q, 1);
914
915
0
    pthread_mutex_unlock(&p->pool_m);
916
917
0
    return 0;
918
0
}
919
920
/*
921
 * Wakes up a single thread stuck in dispatch and make it return with
922
 * errno EAGAIN.
923
 */
924
0
void hts_tpool_wake_dispatch(hts_tpool_process *q) {
925
0
    pthread_mutex_lock(&q->p->pool_m);
926
0
    q->wake_dispatch = 1;
927
0
    pthread_cond_signal(&q->input_not_full_c);
928
0
    pthread_mutex_unlock(&q->p->pool_m);
929
0
}
930
931
/*
932
 * Flushes the process-queue, but doesn't exit. This simply drains the queue
933
 * and ensures all worker threads have finished their current tasks
934
 * associated with this process.
935
 *
936
 * NOT: This does not mean the worker threads are not executing jobs in
937
 * another process-queue.
938
 *
939
 * Returns 0 on success;
940
 *        -1 on failure
941
 */
942
0
int hts_tpool_process_flush(hts_tpool_process *q) {
943
0
    int i;
944
0
    hts_tpool *p = q->p;
945
946
0
    DBG_OUT(stderr, "Flushing pool %p\n", p);
947
948
    // Drains the queue
949
0
    pthread_mutex_lock(&p->pool_m);
950
951
    // Wake up everything for the final sprint!
952
0
    for (i = 0; i < p->tsize; i++)
953
0
        if (p->t_stack[i])
954
0
            pthread_cond_signal(&p->t[i].pending_c);
955
956
    // Ensure there is room for the final sprint.
957
    // Ideally we shouldn't get here, but the "q->qsize - q->n_output >
958
    // n_processing" check in tpool_worker means we can trigger a
959
    // deadlock there.  This negates that possibility.
960
0
    if (q->qsize < q->n_output + q->n_input + q->n_processing)
961
0
        q->qsize = q->n_output + q->n_input + q->n_processing;
962
963
    // When shutdown, we won't be launching more, but we can still
964
    // wait for any processing jobs complete.
965
0
    if (q->shutdown) {
966
0
        while (q->n_processing)
967
0
            pthread_cond_wait(&q->none_processing_c, &p->pool_m);
968
0
    }
969
970
    // Wait for n_input and n_processing to hit zero.
971
0
    while (!q->shutdown && (q->n_input || q->n_processing)) {
972
0
        struct timeval now;
973
0
        struct timespec timeout;
974
975
0
        while (q->n_input && !q->shutdown) {
976
0
            gettimeofday(&now, NULL);
977
0
            timeout.tv_sec = now.tv_sec + 1;
978
0
            timeout.tv_nsec = now.tv_usec * 1000;
979
0
            pthread_cond_timedwait(&q->input_empty_c, &p->pool_m, &timeout);
980
0
        }
981
982
        // Note: even if q->shutdown is set, we still have to wait until
983
        // q->n_processing is zero as we cannot terminate while things are
984
        // running otherwise we free up the data being worked on.
985
0
        while (q->n_processing) {
986
0
            gettimeofday(&now, NULL);
987
0
            timeout.tv_sec = now.tv_sec + 1;
988
0
            timeout.tv_nsec = now.tv_usec * 1000;
989
0
            pthread_cond_timedwait(&q->none_processing_c, &p->pool_m,
990
0
                                   &timeout);
991
0
        }
992
0
        if (q->shutdown) break;
993
0
    }
994
995
0
    pthread_mutex_unlock(&p->pool_m);
996
997
0
    DBG_OUT(stderr, "Flushed complete for pool %p, queue %p\n", p, q);
998
999
0
    return 0;
1000
0
}
1001
1002
/*
1003
 * Resets a process to the initial state.
1004
 *
1005
 * This removes any queued up input jobs, disables any notification of
1006
 * new results/output, flushes what is left and then discards any
1007
 * queued output.  Anything consumer stuck in a wait on results to
1008
 * appear should stay stuck and will only wake up when new data is
1009
 * pushed through the queue.
1010
 *
1011
 * Returns 0 on success;
1012
 *        -1 on failure
1013
 */
1014
0
int hts_tpool_process_reset(hts_tpool_process *q, int free_results) {
1015
0
    hts_tpool_job *j, *jn, *j_head;
1016
0
    hts_tpool_result *r, *rn, *r_head;
1017
1018
0
    pthread_mutex_lock(&q->p->pool_m);
1019
    // prevent next_result from returning data during our flush
1020
0
    q->next_serial = INT_MAX;
1021
1022
    // Remove any queued input not yet being acted upon
1023
0
    j_head = q->input_head;
1024
0
    q->input_head = q->input_tail = NULL;
1025
0
    q->n_input = 0;
1026
1027
    // Remove any queued output, thus ensuring we have room to flush.
1028
0
    r_head = q->output_head;
1029
0
    q->output_head = q->output_tail = NULL;
1030
0
    q->n_output = 0;
1031
0
    pthread_mutex_unlock(&q->p->pool_m);
1032
1033
    // Release memory.  This can be done unlocked now the lists have been
1034
    // removed from the queue
1035
0
    for (j = j_head; j; j = jn) {
1036
0
        jn = j->next;
1037
0
        if (j->job_cleanup) j->job_cleanup(j->arg);
1038
0
        free(j);
1039
0
    }
1040
1041
0
    for (r = r_head; r; r = rn) {
1042
0
        rn = r->next;
1043
0
        if (r->result_cleanup) {
1044
0
            r->result_cleanup(r->data);
1045
0
            r->data = NULL;
1046
0
        }
1047
0
        hts_tpool_delete_result(r, free_results);
1048
0
    }
1049
1050
    // Wait for any jobs being processed to complete.
1051
    // (TODO: consider how to cancel any currently processing jobs.
1052
    // Probably this is too hard.)
1053
0
    if (hts_tpool_process_flush(q) != 0)
1054
0
        return -1;
1055
1056
    // Remove any new output.
1057
0
    pthread_mutex_lock(&q->p->pool_m);
1058
0
    r_head = q->output_head;
1059
0
    q->output_head = q->output_tail = NULL;
1060
0
    q->n_output = 0;
1061
1062
    // Finally reset the serial back to the starting point.
1063
0
    q->next_serial = q->curr_serial = 0;
1064
0
    pthread_cond_signal(&q->input_not_full_c);
1065
0
    pthread_mutex_unlock(&q->p->pool_m);
1066
1067
    // Discard unwanted output
1068
0
    for (r = r_head; r; r = rn) {
1069
        //fprintf(stderr, "Discard output %d\n", r->serial);
1070
0
        rn = r->next;
1071
0
        if (r->result_cleanup) {
1072
0
            r->result_cleanup(r->data);
1073
0
            r->data = NULL;
1074
0
        }
1075
0
        hts_tpool_delete_result(r, free_results);
1076
0
    }
1077
1078
0
    return 0;
1079
0
}
1080
1081
/* Returns the process queue size */
1082
0
int hts_tpool_process_qsize(hts_tpool_process *q) {
1083
0
    return q->qsize;
1084
0
}
1085
1086
/*
1087
 * Destroys a thread pool.  The threads are joined into the main
1088
 * thread so they will finish their current work load.
1089
 */
1090
0
void hts_tpool_destroy(hts_tpool *p) {
1091
0
    int i;
1092
1093
0
    DBG_OUT(stderr, "Destroying pool %p\n", p);
1094
1095
    /* Send shutdown message to worker threads */
1096
0
    pthread_mutex_lock(&p->pool_m);
1097
0
    p->shutdown = 1;
1098
1099
0
    DBG_OUT(stderr, "Sending shutdown request\n");
1100
1101
0
    for (i = 0; i < p->tsize; i++)
1102
0
        pthread_cond_signal(&p->t[i].pending_c);
1103
1104
0
    pthread_mutex_unlock(&p->pool_m);
1105
1106
0
    DBG_OUT(stderr, "Shutdown complete\n");
1107
1108
0
    for (i = 0; i < p->tsize; i++)
1109
0
        pthread_join(p->t[i].tid, NULL);
1110
1111
0
    pthread_mutex_destroy(&p->pool_m);
1112
0
    for (i = 0; i < p->tsize; i++)
1113
0
        pthread_cond_destroy(&p->t[i].pending_c);
1114
1115
0
    if (p->t_stack)
1116
0
        free(p->t_stack);
1117
1118
0
    free(p->t);
1119
0
    free(p);
1120
1121
0
    DBG_OUT(stderr, "Destroyed pool %p\n", p);
1122
0
}
1123
1124
1125
/*
1126
 * Destroys a thread pool without waiting on jobs to complete.
1127
 * Use hts_tpool_kill(p) to quickly exit after a fatal error.
1128
 */
1129
0
void hts_tpool_kill(hts_tpool *p) {
1130
0
    int i;
1131
1132
0
    DBG_OUT(stderr, "Destroying pool %p, kill=%d\n", p, kill);
1133
1134
0
    for (i = 0; i < p->tsize; i++)
1135
0
        pthread_kill(p->t[i].tid, SIGINT);
1136
1137
0
    pthread_mutex_destroy(&p->pool_m);
1138
0
    for (i = 0; i < p->tsize; i++)
1139
0
        pthread_cond_destroy(&p->t[i].pending_c);
1140
1141
0
    if (p->t_stack)
1142
0
        free(p->t_stack);
1143
1144
0
    free(p->t);
1145
0
    free(p);
1146
1147
0
    DBG_OUT(stderr, "Destroyed pool %p\n", p);
1148
0
}
1149
1150
1151
/*=============================================================================
1152
 * Test app.
1153
 *
1154
 * This can be considered both as a basic test and as a worked example for
1155
 * various usage patterns.
1156
 *=============================================================================
1157
 */
1158
1159
#ifdef TEST_MAIN
1160
1161
#include <stdio.h>
1162
#include "hts_internal.h"
1163
1164
#ifndef TASK_SIZE
1165
#define TASK_SIZE 1000
1166
#endif
1167
1168
/*-----------------------------------------------------------------------------
1169
 * Unordered x -> x*x test.
1170
 * Results arrive in order of completion.
1171
 */
1172
void *doit_square_u(void *arg) {
1173
    int job = *(int *)arg;
1174
1175
    hts_usleep(random() % 100000); // to coerce job completion out of order
1176
1177
    printf("RESULT: %d\n", job*job);
1178
1179
    free(arg);
1180
    return NULL;
1181
}
1182
1183
int test_square_u(int n) {
1184
    hts_tpool *p = hts_tpool_init(n);
1185
    hts_tpool_process *q = hts_tpool_process_init(p, n*2, 1);
1186
    int i;
1187
1188
    // Dispatch jobs
1189
    for (i = 0; i < TASK_SIZE; i++) {
1190
        int *ip = malloc(sizeof(*ip));
1191
        *ip = i;
1192
        hts_tpool_dispatch(p, q, doit_square_u, ip);
1193
    }
1194
1195
    hts_tpool_process_flush(q);
1196
    hts_tpool_process_destroy(q);
1197
    hts_tpool_destroy(p);
1198
1199
    return 0;
1200
}
1201
1202
1203
/*-----------------------------------------------------------------------------
1204
 * Ordered x -> x*x test.
1205
 * Results arrive in numerical order.
1206
 *
1207
 * This implementation uses a non-blocking dispatch to avoid dead-locks
1208
 * where one job takes too long to complete.
1209
 */
1210
void *doit_square(void *arg) {
1211
    int job = *(int *)arg;
1212
    int *res;
1213
1214
    // One excessively slow, to stress test output queue filling and
1215
    // excessive out of order scenarios.
1216
    hts_usleep(500000 * ((job&31)==31) + random() % 10000);
1217
1218
    res = malloc(sizeof(*res));
1219
    *res = (job<0) ? -job*job : job*job;
1220
1221
    free(arg);
1222
    return res;
1223
}
1224
1225
int test_square(int n) {
1226
    hts_tpool *p = hts_tpool_init(n);
1227
    hts_tpool_process *q = hts_tpool_process_init(p, n*2, 0);
1228
    int i;
1229
    hts_tpool_result *r;
1230
1231
    // Dispatch jobs
1232
    for (i = 0; i < TASK_SIZE; i++) {
1233
        int *ip = malloc(sizeof(*ip));
1234
        *ip = i;
1235
        int blk;
1236
1237
        do {
1238
            // In the situation where some jobs take much longer than
1239
            // others, we could end up blocking here as we haven't got
1240
            // any room in the output queue to place it. (We don't launch a
1241
            // job if the output queue is full.)
1242
1243
            // This happens when the next serial number to fetch is, eg, 50
1244
            // but jobs 51-100 have all executed really fast and appeared in
1245
            // the output queue before 50.  A dispatch & check-results
1246
            // alternating loop can fail to find job 50 many times over until
1247
            // eventually the dispatch blocks before it arrives.
1248
1249
            // Our solution is to dispatch in non-blocking mode so we are
1250
            // always to either dispatch or consume a result.
1251
            blk = hts_tpool_dispatch2(p, q, doit_square, ip, 1);
1252
1253
            // Check for results.
1254
            if ((r = hts_tpool_next_result(q))) {
1255
                printf("RESULT: %d\n", *(int *)hts_tpool_result_data(r));
1256
                hts_tpool_delete_result(r, 1);
1257
            }
1258
            if (blk == -1) {
1259
                // The alternative is a separate thread for dispatching and/or
1260
                // consumption of results. See test_squareB.
1261
                putchar('.'); fflush(stdout);
1262
                hts_usleep(10000);
1263
            }
1264
        } while (blk == -1);
1265
    }
1266
1267
    // Wait for any input-queued up jobs or in-progress jobs to complete.
1268
    hts_tpool_process_flush(q);
1269
1270
    while ((r = hts_tpool_next_result(q))) {
1271
        printf("RESULT: %d\n", *(int *)hts_tpool_result_data(r));
1272
        hts_tpool_delete_result(r, 1);
1273
    }
1274
1275
    hts_tpool_process_destroy(q);
1276
    hts_tpool_destroy(p);
1277
1278
    return 0;
1279
}
1280
1281
/*-----------------------------------------------------------------------------
1282
 * Ordered x -> x*x test.
1283
 * Results arrive in numerical order.
1284
 *
1285
 * This implementation uses separate dispatching threads and job consumption
1286
 * threads (main thread).  This means it can use a blocking calls for
1287
 * simplicity elsewhere.
1288
 */
1289
struct squareB_opt {
1290
    hts_tpool *p;
1291
    hts_tpool_process *q;
1292
    int n;
1293
};
1294
static void *test_squareB_dispatcher(void *arg) {
1295
    struct squareB_opt *o = (struct squareB_opt *)arg;
1296
    int i, *ip;
1297
1298
    for (i = 0; i < o->n; i++) {
1299
        ip = malloc(sizeof(*ip));
1300
        *ip = i;
1301
1302
        hts_tpool_dispatch(o->p, o->q, doit_square, ip);
1303
    }
1304
1305
    // Dispatch an sentinel job to mark the end
1306
    *(ip = malloc(sizeof(*ip))) = -1;
1307
    hts_tpool_dispatch(o->p, o->q, doit_square, ip);
1308
    pthread_exit(NULL);
1309
}
1310
1311
int test_squareB(int n) {
1312
    hts_tpool *p = hts_tpool_init(n);
1313
    hts_tpool_process *q = hts_tpool_process_init(p, n*2, 0);
1314
    struct squareB_opt o = {p, q, TASK_SIZE};
1315
    pthread_t tid;
1316
1317
    // Launch our job creation thread.
1318
    pthread_create(&tid, NULL, test_squareB_dispatcher, &o);
1319
1320
    // Consume all results until we find the end-of-job marker.
1321
    for(;;) {
1322
        hts_tpool_result *r = hts_tpool_next_result_wait(q);
1323
        int x = *(int *)hts_tpool_result_data(r);
1324
        hts_tpool_delete_result(r, 1);
1325
        if (x == -1)
1326
            break;
1327
        printf("RESULT: %d\n", x);
1328
    }
1329
1330
    // Wait for any input-queued up jobs or in-progress jobs to complete.
1331
    // This should do nothing as we've been executing until the termination
1332
    // marker of -1.
1333
    hts_tpool_process_flush(q);
1334
    assert(hts_tpool_next_result(q) == NULL);
1335
1336
    hts_tpool_process_destroy(q);
1337
    hts_tpool_destroy(p);
1338
    pthread_join(tid, NULL);
1339
1340
    return 0;
1341
}
1342
1343
1344
/*-----------------------------------------------------------------------------
1345
 * A simple pipeline test.
1346
 * We use a dedicated input thread that does the initial generation of job
1347
 * and dispatch, several execution steps running in a shared pool, and a
1348
 * dedicated output thread that prints up the final result.  It's key that our
1349
 * pipeline execution stages can run independently and don't themselves have
1350
 * any waits.  To achieve this we therefore also use some dedicated threads
1351
 * that take the output from one queue and resubmits the job as the input to
1352
 * the next queue.
1353
 *
1354
 * More generally this could perhaps be a single pipeline thread that
1355
 * marshalls multiple queues and their interactions, but this is simply a
1356
 * demonstration of a single pipeline.
1357
 *
1358
 * Our process fills out the bottom byte of a 32-bit int and then shifts it
1359
 * left one byte at a time.  Only the final stage needs to be ordered.  Each
1360
 * stage uses its own queue.
1361
 *
1362
 * Possible improvement: we only need the last stage to be ordered.  By
1363
 * allocating our own serial numbers for the first job and manually setting
1364
 * these serials in the last job, perhaps we can permit out of order execution
1365
 * of all the in-between stages.  (I doubt it'll affect speed much though.)
1366
 */
1367
1368
static void *pipe_input_thread(void *arg);
1369
static void *pipe_stage1(void *arg);
1370
static void *pipe_stage2(void *arg);
1371
static void *pipe_stage3(void *arg);
1372
static void *pipe_output_thread(void *arg);
1373
1374
typedef struct {
1375
    hts_tpool *p;
1376
    hts_tpool_process *q1;
1377
    hts_tpool_process *q2;
1378
    hts_tpool_process *q3;
1379
    int n;
1380
} pipe_opt;
1381
1382
typedef struct {
1383
    pipe_opt *o;
1384
    unsigned int x;
1385
    int eof; // set with last job.
1386
} pipe_job;
1387
1388
static void *pipe_input_thread(void *arg) {
1389
    pipe_opt *o = (pipe_opt *)arg;
1390
1391
    int i;
1392
    for (i = 1; i <= o->n; i++) {
1393
        pipe_job *j = malloc(sizeof(*j));
1394
        j->o = o;
1395
        j->x = i;
1396
        j->eof = (i == o->n);
1397
1398
        printf("I  %08x\n", j->x);
1399
1400
        if (hts_tpool_dispatch(o->p, o->q1, pipe_stage1, j) != 0) {
1401
            free(j);
1402
            pthread_exit((void *)1);
1403
        }
1404
    }
1405
1406
    pthread_exit(NULL);
1407
}
1408
1409
static void *pipe_stage1(void *arg) {
1410
    pipe_job *j = (pipe_job *)arg;
1411
1412
    j->x <<= 8;
1413
    hts_usleep(random() % 10000); // fast job
1414
    printf("1  %08x\n", j->x);
1415
1416
    return j;
1417
}
1418
1419
static void *pipe_stage1to2(void *arg) {
1420
    pipe_opt *o = (pipe_opt *)arg;
1421
    hts_tpool_result *r;
1422
1423
    while ((r = hts_tpool_next_result_wait(o->q1))) {
1424
        pipe_job *j = (pipe_job *)hts_tpool_result_data(r);
1425
        hts_tpool_delete_result(r, 0);
1426
        if (hts_tpool_dispatch(j->o->p, j->o->q2, pipe_stage2, j) != 0)
1427
            pthread_exit((void *)1);
1428
        if (j->eof)
1429
            break;
1430
    }
1431
1432
    pthread_exit(NULL);
1433
}
1434
1435
static void *pipe_stage2(void *arg) {
1436
    pipe_job *j = (pipe_job *)arg;
1437
1438
    j->x <<= 8;
1439
    hts_usleep(random() % 100000); // slow job
1440
    printf("2  %08x\n", j->x);
1441
1442
    return j;
1443
}
1444
1445
static void *pipe_stage2to3(void *arg) {
1446
    pipe_opt *o = (pipe_opt *)arg;
1447
    hts_tpool_result *r;
1448
1449
    while ((r = hts_tpool_next_result_wait(o->q2))) {
1450
        pipe_job *j = (pipe_job *)hts_tpool_result_data(r);
1451
        hts_tpool_delete_result(r, 0);
1452
        if (hts_tpool_dispatch(j->o->p, j->o->q3, pipe_stage3, j) != 0)
1453
            pthread_exit((void *)1);
1454
        if (j->eof)
1455
            break;
1456
    }
1457
1458
    pthread_exit(NULL);
1459
}
1460
1461
static void *pipe_stage3(void *arg) {
1462
    pipe_job *j = (pipe_job *)arg;
1463
1464
    hts_usleep(random() % 10000); // fast job
1465
    j->x <<= 8;
1466
    return j;
1467
}
1468
1469
static void *pipe_output_thread(void *arg) {
1470
    pipe_opt *o = (pipe_opt *)arg;
1471
    hts_tpool_result *r;
1472
1473
    while ((r = hts_tpool_next_result_wait(o->q3))) {
1474
        pipe_job *j = (pipe_job *)hts_tpool_result_data(r);
1475
        int eof = j->eof;
1476
        printf("O  %08x\n", j->x);
1477
        hts_tpool_delete_result(r, 1);
1478
        if (eof)
1479
            break;
1480
    }
1481
1482
    pthread_exit(NULL);
1483
}
1484
1485
int test_pipe(int n) {
1486
    hts_tpool *p = hts_tpool_init(n);
1487
    hts_tpool_process *q1 = hts_tpool_process_init(p, n*2, 0);
1488
    hts_tpool_process *q2 = hts_tpool_process_init(p, n*2, 0);
1489
    hts_tpool_process *q3 = hts_tpool_process_init(p, n*2, 0);
1490
    pipe_opt o = {p, q1, q2, q3, TASK_SIZE};
1491
    pthread_t tidIto1, tid1to2, tid2to3, tid3toO;
1492
    void *retv;
1493
    int ret;
1494
1495
    // Launch our data source and sink threads.
1496
    pthread_create(&tidIto1, NULL, pipe_input_thread,  &o);
1497
    pthread_create(&tid1to2, NULL, pipe_stage1to2,     &o);
1498
    pthread_create(&tid2to3, NULL, pipe_stage2to3,     &o);
1499
    pthread_create(&tid3toO, NULL, pipe_output_thread, &o);
1500
1501
    // Wait for tasks to finish.
1502
    ret = 0;
1503
    pthread_join(tidIto1, &retv); ret |= (retv != NULL);
1504
    pthread_join(tid1to2, &retv); ret |= (retv != NULL);
1505
    pthread_join(tid2to3, &retv); ret |= (retv != NULL);
1506
    pthread_join(tid3toO, &retv); ret |= (retv != NULL);
1507
    printf("Return value %d\n", ret);
1508
1509
    hts_tpool_process_destroy(q1);
1510
    hts_tpool_process_destroy(q2);
1511
    hts_tpool_process_destroy(q3);
1512
    hts_tpool_destroy(p);
1513
1514
    return 0;
1515
}
1516
1517
/*-----------------------------------------------------------------------------*/
1518
int main(int argc, char **argv) {
1519
    int n;
1520
    srandom(0);
1521
1522
    if (argc < 3) {
1523
        fprintf(stderr, "Usage: %s command n_threads\n", argv[0]);
1524
        fprintf(stderr, "Where commands are:\n\n");
1525
        fprintf(stderr, "unordered       # Unordered output\n");
1526
        fprintf(stderr, "ordered1        # Main thread with non-block API\n");
1527
        fprintf(stderr, "ordered2        # Dispatch thread, blocking API\n");
1528
        fprintf(stderr, "pipe            # Multi-stage pipeline, several queues\n");
1529
        exit(1);
1530
    }
1531
1532
    n = atoi(argv[2]);
1533
    if (strcmp(argv[1], "unordered") == 0) return test_square_u(n);
1534
    if (strcmp(argv[1], "ordered1") == 0)  return test_square(n);
1535
    if (strcmp(argv[1], "ordered2") == 0)  return test_squareB(n);
1536
    if (strcmp(argv[1], "pipe") == 0)      return test_pipe(n);
1537
1538
    fprintf(stderr, "Unknown sub-command\n");
1539
    exit(1);
1540
}
1541
#endif