1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3 * This file and its contents are supplied under the terms of the
4 * Common Development and Distribution License ("CDDL"), version 1.0.
5 * You may only use this file in accordance with the terms of version
6 * 1.0 of the CDDL.
7 *
8 * A full copy of the text of the CDDL should have accompanied this
9 * source. A copy of the CDDL is also available via the Internet at
10 * https://opensource.org/license/CDDL-1.0.
11 */
12
13 /*
14 * Copyright (c) 2026 by Garth Snyder. All rights reserved.
15 */
16
17 #include <assert.h>
18 #include <atomic.h>
19 #include <err.h>
20 #include <errno.h>
21 #include <pthread.h>
22 #include <sched.h>
23 #include <stddef.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/param.h>
29 #include <sys/random.h>
30 #include <sys/stdtypes.h>
31 #include <sys/sysmacros.h>
32 #include <sys/time.h>
33 #include <time.h>
34 #include <unistd.h>
35
36 #include "zstream_queue.h"
37 #include "zstream_util.h"
38
39 #define ENQUEUE_DELAY_NSEC (100 * 1000) /* 100us */
40 #define DISPATCH_BACKUP_NSEC (1000 * 1000) /* 1ms */
41
42 #define PLENTY_OF_WORK 6 /* "Many" items to claim */
43 #define NO_WORK 1.0E-6 /* No-work score threshold */
44 #define DEQUEUE_SCORE_WEIGHT 0.3 /* Dequeue score relative weight */
45
46 #define Q_MOD(queue, index) ((index) % (queue)->zq_params.qp_queue_length)
47 #define Q_SLOT(queue, index) ((queue)->zq_slots[Q_MOD((queue), (index))])
48
49 #define Q_FULL(queue) ((queue)->zq_ix.enqueue - (queue)->zq_ix.dequeue >= \
50 (queue)->zq_params.qp_queue_length)
51
52 /*
53 * A zstream_queue is a ring buffer with four indexes: enqueue, claim,
54 * complete, and dequeue, in that order. No index can move beyond its
55 * preceding index. Every interval between indexes contains work items in a
56 * particular state: enqueued, claimed for work, or completed. Items never
57 * leave the ring buffer, so FIFO order is guaranteed on dequeueing.
58 *
59 * In concept, every index has a corresponding condition that threads can
60 * wait on if they are interested in knowing when that index moves:
61 * enqueued, claimed, completed, dequeued. However, the reality deviates
62 * from this model in two ways:
63 *
64 * - There is no "claimed" condition, because no thread would wait on it.
65 * Claiming and processing are one unified operation. Dequeuers await the
66 * "completed" condition.
67 *
68 * - All queues share one thread pool, so idle threads are not bound to any
69 * particular queue. Instead of having queue-specific "enqueued" conditions,
70 * queues share a centralized dispatch system. On being awakened, worker
71 * threads assign themselves to a queue through a scoring mechanism
72 * described in the comments at score_queue().
73 *
74 * LOCKING
75 *
76 * There are three types of lock:
77 *
78 * - One global lock that gates changes to the thread pool and queue cohort.
79 * This lock also acts as the mutex for the tp_wake_worker condition.
80 *
81 * - A second, low-contention global lock that protects the dispatch system
82 *
83 * - One lock for each queue
84 *
85 * Any operation that adds or removes queues or threads should hold the pool
86 * lock. Any operation that moves a queue's indexes should hold the queue
87 * lock. Any thread waiting for work waits on tp_wake_worker.
88 *
89 * Worker threads hold no locks while they are actually processing items.
90 *
91 * The global locking order is dispatch -> pool -> queue.
92 *
93 * DISPATCH
94 *
95 * Four events trigger dispatch loops:
96 *
97 * 1) A worker thread completing its batch. Threads always check to see if
98 * there's more claimable work before going to sleep.
99 *
100 * 2) A worker thread discovering more work than it can handle on its own.
101 * Before starting work on its own batch, the worker attempts to signal
102 * another thread to wake up and assess the current state.
103 *
104 * 3) Enqueues. These go through the dispatch system and are batched.
105 * Roughly ENQUEUE_DELAY_NSEC after an enqueue (on any queue), the dispatch
106 * thread attempts to awaken a worker.
107 *
108 * 4) The expiration of a backup timer. The atomic value tp_unclaimed tracks
109 * the total number of enqueued-but-unclaimed items across all queues. When
110 * zero, it indicates that no worker dispatch is currently necessary. This
111 * value is rigorously maintained and the increments and decrements are
112 * sequentially consistent. However, the reads are relaxed, so a reader may
113 * see a stale value. In the event that a critical worker wakeup is dropped,
114 * the backup timer intervenes to keep dispatches running.
115 */
116
117 typedef struct {
118 queue_item_t *qs_item;
119 size_t qs_cost;
120 boolean_t qs_completed;
121 boolean_t qs_end_of_stream;
122 } queue_slot_t;
123
124 typedef struct {
125 uint64_t enqueue;
126 uint64_t claim;
127 uint64_t complete;
128 uint64_t dequeue;
129 } zq_indexes_t;
130
131 typedef struct {
132 pthread_cond_t completed;
133 pthread_cond_t dequeued;
134 } zq_conditions_t;
135
136 typedef struct {
137 int min_depth;
138 int max_depth;
139 } zq_stats_t;
140
141 struct zstream_queue {
142 int zq_id;
143 queue_slot_t *zq_slots;
144 pthread_mutex_t zq_mutex;
145 zq_indexes_t zq_ix;
146 zq_conditions_t zq_cond;
147 zq_params_t zq_params;
148 boolean_t zq_disallow_enqueue;
149 #ifdef MONITOR_QUEUES
150 zq_stats_t zq_stats;
151 uint64_t zq_histogram[ZQ_MAX_BATCH+1]; /* Batch sizes */
152 #endif
153 };
154
155 typedef struct {
156 pthread_mutex_t tp_pool_mutex;
157 pthread_cond_t tp_wake_worker; /* Awaited by workers */
158
159 pthread_mutex_t tp_dispatch_mutex;
160 pthread_cond_t tp_request_dispatch; /* By dispatch thread */
161 boolean_t tp_dispatch_requested;
162
163 zstream_queue_t *tp_queues[ZQ_MAX_QUEUES];
164 int tp_num_queues;
165
166 boolean_t tp_threads_created;
167 int tp_num_threads;
168
169 uint64_t tp_unclaimed; /* Atomic, all queues */
170 } thread_pool_t;
171
172 typedef union {
173 long long ll;
174 long double ld;
175 void *p;
176 void (*fp)(void);
177 } worst_case_alignment_t;
178
179 static void *queue_worker(void *);
180 static void *dispatch_worker(void *);
181
182 #ifdef MONITOR_QUEUES
183 static void *cpu_and_queue_monitor(void *);
184 static void print_batch_size_histogram(zstream_queue_t *);
185 #endif
186
187 static thread_pool_t pool = {0};
188 static pthread_once_t once_control = PTHREAD_ONCE_INIT;
189
190 /*
191 * The dispatch timer needs sub-millisecond accuracy. POSIX timers on
192 * FreeBSD don't implement that, but nanosleep() works fine.
193 */
194 static void
sleep_nsec(uint64_t nsec)195 sleep_nsec(uint64_t nsec)
196 {
197 struct timespec ts = {
198 .tv_sec = nsec / NANOSEC,
199 .tv_nsec = nsec % NANOSEC
200 };
201 while (nanosleep(&ts, &ts) != 0) {
202 if (errno != EINTR)
203 err(1, "nanosleep failed");
204 }
205 }
206
207 static void
thread_pool_init(void)208 thread_pool_init(void)
209 {
210 pthread_mutex_init(&pool.tp_pool_mutex, NULL);
211 pthread_cond_init(&pool.tp_wake_worker, NULL);
212
213 pthread_mutex_init(&pool.tp_dispatch_mutex, NULL);
214 pthread_cond_init(&pool.tp_request_dispatch, NULL);
215
216 safe_create_thread(dispatch_worker, NULL, "dispatch", B_TRUE);
217 }
218
219 /*
220 * If this function is to be called at all, it must be called before any
221 * queues have been created.
222 */
223 void
zstream_queue_set_num_threads(int n)224 zstream_queue_set_num_threads(int n)
225 {
226 pthread_once(&once_control, thread_pool_init);
227 pthread_mutex_lock(&pool.tp_pool_mutex);
228 if (pool.tp_threads_created) {
229 errx(1, "thread pool size must be set before creating queues");
230 } else if (n < 1) {
231 errx(1, "number of threads must be at least 1");
232 } else if (n < ZQ_MIN_THREADS) {
233 warnx("using only %d threads may limit performance, setting "
234 "anyway...", n);
235 } else if (n > 256) {
236 warnx("num_threads = %d seems suspiciously high, setting "
237 "anyway...", n);
238 }
239 pool.tp_num_threads = n;
240 pthread_mutex_unlock(&pool.tp_pool_mutex);
241 }
242
243 /*
244 * Locking: the caller must hold the pool mutex.
245 *
246 * If tp_num_threads is nonzero, it sets the number of threads to spawn.
247 * Otherwise, one thread is spawned per core, with a minimum of 6 threads.
248 *
249 * sched_getaffinity() is a better estimate of available threads than
250 * sysconf because sysconf doesn't account for limits that might be set on,
251 * e.g., a container.
252 */
253 static void
thread_pool_spinup(void)254 thread_pool_spinup(void)
255 {
256 if (pool.tp_num_threads == 0) {
257 #ifdef CPU_COUNT
258 cpu_set_t cpu_set;
259 if (sched_getaffinity(0, sizeof (cpu_set_t), &cpu_set) != 0) {
260 warn("sched_getaffinity failed, using sysconf");
261 pool.tp_num_threads = sysconf(_SC_NPROCESSORS_ONLN);
262 } else {
263 pool.tp_num_threads = CPU_COUNT(&cpu_set);
264 }
265 #else
266 pool.tp_num_threads = sysconf(_SC_NPROCESSORS_ONLN);
267 #endif
268 pool.tp_num_threads = MAX(pool.tp_num_threads, ZQ_MIN_THREADS);
269 }
270 for (int i = 0; i < pool.tp_num_threads; i++) {
271 char name[32];
272 snprintf(name, sizeof (name), "queue-%d", i);
273 safe_create_thread(queue_worker, NULL, name, B_TRUE);
274 }
275 #ifdef MONITOR_QUEUES
276 safe_create_thread(cpu_and_queue_monitor, NULL, "monitor", B_TRUE);
277 #endif
278 }
279
280 zstream_queue_t *
zstream_queue_create(zq_params_t * params)281 zstream_queue_create(zq_params_t *params)
282 {
283 static int next_queue_id = 0;
284
285 VERIFY3P(params->qp_process, !=, NULL);
286 VERIFY3P(params->qp_cost, !=, NULL);
287 VERIFY3U(params->qp_item_size, >, 0);
288 VERIFY3U(params->qp_queue_length, >, 0);
289 VERIFY3U(params->qp_queue_length, <, 1 << 18);
290
291 pthread_once(&once_control, thread_pool_init);
292 pthread_mutex_lock(&pool.tp_pool_mutex);
293 VERIFY3S(pool.tp_num_queues, <, ZQ_MAX_QUEUES);
294
295 if (!pool.tp_threads_created) {
296 thread_pool_spinup();
297 pool.tp_threads_created = B_TRUE;
298 }
299
300 zstream_queue_t *queue = safe_malloc(sizeof (zstream_queue_t));
301 *queue = (zstream_queue_t) {
302 .zq_id = next_queue_id++,
303 .zq_params = *params,
304 .zq_slots = safe_malloc(params->qp_queue_length *
305 (sizeof (queue_slot_t))),
306 #ifdef MONITOR_QUEUES
307 .zq_stats.min_depth = INT_MAX
308 #endif
309 };
310 pool.tp_queues[pool.tp_num_queues] = queue;
311
312 size_t qpis_rounded = P2ROUNDUP(params->qp_item_size,
313 _Alignof(worst_case_alignment_t));
314 uint8_t *items = safe_malloc(params->qp_queue_length * qpis_rounded);
315 for (size_t i = 0; i < params->qp_queue_length; i++) {
316 queue->zq_slots[i].qs_item =
317 (queue_item_t *)(items + i * qpis_rounded);
318 }
319
320 pthread_mutex_init(&queue->zq_mutex, NULL);
321 pthread_cond_init(&queue->zq_cond.completed, NULL);
322 pthread_cond_init(&queue->zq_cond.dequeued, NULL);
323
324 pool.tp_num_queues++;
325 pthread_mutex_unlock(&pool.tp_pool_mutex);
326 return (queue);
327 }
328
329 /*
330 * Try to advance the "claim" and "complete" indexes as far as possible by
331 * examining the qs_completed flag on each item. This can't be done directly
332 * by the threads that complete work, for a couple of reasons:
333 *
334 * - Items can be completed in any order. Just because you (a thread) have
335 * finished your batch doesn't mean that all prior batches have completed.
336 * If there are uncompleted items ahead of you in the ring buffer, you can't
337 * advance the completion index past them on your way out.
338 *
339 * - Items for which the cost function returns 0 are marked as qs_completed
340 * on enqueue and are never seen by a worker thread. So, there needs to be
341 * an independent mechanism to sweep the completion index past these items
342 * whenever that becomes possible.
343 *
344 * This function is called:
345 *
346 * - Whenever a thread completes a batch
347 * - Whenever a thread claims a batch
348 * - Whenever an item of cost 0 is enqueued
349 *
350 * Strictly speaking, advancing on claiming a batch is not logically
351 * necessary. However, the claimer already holds the queue mutex, and it's
352 * in our interest to make completed items available for dequeueing as
353 * expeditiously as possible.
354 *
355 * Sweeping of the "claim" index is also an optimization. It is not
356 * necessary for correctness. However, if we don't do it here, it can only
357 * be done by threads as they claim jobs to work on. In some cases, not
358 * advancing the "claim" index here can result in an empty batch and a
359 * wasted claim cycle.
360 *
361 * Locking: the caller must hold the queue mutex.
362 */
363 static inline void
advance_indexes(zstream_queue_t * queue)364 advance_indexes(zstream_queue_t *queue)
365 {
366 boolean_t any_completed = B_FALSE;
367 uint64_t claimed = 0;
368
369 while (queue->zq_ix.claim < queue->zq_ix.enqueue &&
370 Q_SLOT(queue, queue->zq_ix.claim).qs_completed) {
371 queue->zq_ix.claim++;
372 claimed++;
373 }
374 if (claimed > 0) {
375 /*
376 * tp_unclaimed is decremented both here and in
377 * claim_batch(). The conditions are mutually exclusive, so
378 * double counting will not occur.
379 */
380 atomic_sub_64(&pool.tp_unclaimed, claimed);
381 }
382 while (queue->zq_ix.complete < queue->zq_ix.claim &&
383 Q_SLOT(queue, queue->zq_ix.complete).qs_completed) {
384 queue->zq_ix.complete++;
385 any_completed = B_TRUE;
386 }
387 if (any_completed) {
388 pthread_cond_signal(&queue->zq_cond.completed);
389 }
390 }
391
392 /*
393 * Score a queue according to its need for workers. Higher is better. The
394 * scoring tries to assign threads to queues that are running out of space
395 * for new enqueuements or that have little completed work available to
396 * dequeue. The broader goal is to try to avoid pipeline stalls.
397 *
398 * Two measures are used for scoring. The "open score" is 1/M where M is the
399 * number of slots available to receive new items. The "dequeue score" is
400 * 1/N where N is the number of completed items available to dequeue. These
401 * two measures are added together with the dequeue score scaled by
402 * DEQUEUE_SCORE_WEIGHT.
403 *
404 * The composite score is scaled by a factor that reflects how much work is
405 * actually available to be claimed on the queue; there's no point assigning
406 * threads to queues that have no work.
407 *
408 * Locking: the caller must hold the thread pool mutex and the queue mutex.
409 */
410 static inline double
score_queue(zstream_queue_t * queue)411 score_queue(zstream_queue_t *queue)
412 {
413 uint64_t claimable = queue->zq_ix.enqueue - queue->zq_ix.claim;
414 uint64_t dequeueable = queue->zq_ix.complete - queue->zq_ix.dequeue;
415 uint64_t in_queue = queue->zq_ix.enqueue - queue->zq_ix.dequeue;
416 uint64_t open_slots = queue->zq_params.qp_queue_length - in_queue;
417
418 double open_score = (open_slots > 0) ? (1.0 / open_slots) : 2.0;
419 double dq_score = (dequeueable > 0) ? (1.0 / dequeueable) : 2.0;
420 double claim_factor = MIN(claimable, (uint64_t)PLENTY_OF_WORK) /
421 (double)PLENTY_OF_WORK;
422 double need = open_score + dq_score * DEQUEUE_SCORE_WEIGHT;
423 return (need * claim_factor);
424 }
425
426 /*
427 * Return a random index from an array of doubles, with the likelihood of
428 * index i being selected equal to weights[i] / sum(weights). Returns index
429 * 0 if no weight is greater than 0.
430 */
431 static inline int
select_stochastic(double weights[],int num_values)432 select_stochastic(double weights[], int num_values)
433 {
434 const double denominator = (double)UINT64_MAX;
435 uint64_t numerator;
436 double total = 0.0;
437
438 for (int i = 0; i < num_values; i++) {
439 total += weights[i];
440 }
441 random_get_pseudo_bytes((uint8_t *)&numerator, sizeof (numerator));
442 double select_val = total * numerator / denominator;
443 for (int i = 0; i < num_values; i++) {
444 if (select_val < weights[i])
445 return (i);
446 select_val -= weights[i];
447 }
448 /* Fallback in case of FP rounding not producing a winner */
449 for (int i = num_values - 1; i >= 0; i--) {
450 if (weights[i] != 0.0)
451 return (i);
452 }
453 return (0);
454 }
455
456 /*
457 * Claim up to ZQ_MAX_BATCH work items from the given queue, trying to
458 * accumulate at least qp_batch_budget worth of work data (== "cost"). All
459 * items in a batch will be drawn from the same queue.
460 *
461 * Does not block waiting to fill the budget; returns whatever is available.
462 *
463 * Locking: this function must be called with both the queue mutex and the
464 * thread pool mutex held. zstream_queue_destroy() can't hold a queue's
465 * mutex while destroying it (because destruction entails destroying the
466 * queue mutex, which must be unlocked), so holding the queue mutex while
467 * attempting to claim work is not a sufficient guarantee of correctness.
468 *
469 * In other contexts, we have more certainty about whether a queue still has
470 * work to do. If it does, it can't be destroyed while we hold the queue
471 * mutex alone. But here, we merely suspect that there's work available
472 * based on possibly outdated queue scoring information. By the time we get
473 * here, the queue might already have been finalized. Holding the thread
474 * pool mutex guarantees that the queue won't have been destroyed out from
475 * under us.
476 */
477 static int
claim_batch(zstream_queue_t * queue,queue_slot_t ** batch)478 claim_batch(zstream_queue_t *queue, queue_slot_t **batch)
479 {
480 size_t cost_claimed = 0;
481 int count = 0;
482 uint64_t passed = 0;
483 boolean_t more_to_claim, more_slots, more_budget;
484 boolean_t first_and_only, ok_to_claim;
485
486 while (B_TRUE) {
487 more_to_claim = queue->zq_ix.claim < queue->zq_ix.enqueue;
488 more_slots = count < ZQ_MAX_BATCH;
489 more_budget = cost_claimed < queue->zq_params.qp_batch_budget;
490 first_and_only = queue->zq_params.qp_batch_budget == 0 &&
491 count == 0;
492 ok_to_claim = first_and_only || more_budget;
493
494 if (!more_to_claim || !more_slots || !ok_to_claim) {
495 break;
496 }
497 queue_slot_t *slot = &Q_SLOT(queue, queue->zq_ix.claim);
498 if (!slot->qs_completed) {
499 cost_claimed += slot->qs_cost;
500 batch[count++] = slot;
501 }
502 queue->zq_ix.claim++;
503 passed++;
504 }
505
506 /*
507 * Every slot the claim index moved over leaves the unclaimed pool,
508 * whether we took it for the batch or skipped it as already complete.
509 */
510 if (passed > 0) {
511 atomic_sub_64(&pool.tp_unclaimed, passed);
512 }
513 advance_indexes(queue);
514 #ifdef MONITOR_QUEUES
515 queue->zq_histogram[count]++;
516 #endif
517 return (count);
518 }
519
520 /*
521 * Threads are assigned to a queue on each loop so they can be shifted
522 * dynamically to follow available work. Idle threads will typically be
523 * waiting on the tp_wake_worker condition within this function.
524 *
525 * Locking: we hold the pool mutex throughout, both to keep a queue from
526 * being destroyed out from under us while we score it or claim from it, and
527 * because it is the mutex for tp_wake_worker. Individual queues are locked
528 * for only as long as it takes to score or claim from them.
529 */
530 static int
assign_queue_and_get_work(zstream_queue_t ** queue,queue_slot_t ** batch)531 assign_queue_and_get_work(zstream_queue_t **queue, queue_slot_t **batch)
532 {
533 pthread_mutex_lock(&pool.tp_pool_mutex);
534
535 while (B_TRUE) {
536 int num_queues = pool.tp_num_queues;
537 double weights[ZQ_MAX_QUEUES];
538 int queues_with_work = 0;
539
540 for (int i = 0; i < num_queues; i++) {
541 zstream_queue_t *to_score = pool.tp_queues[i];
542 pthread_mutex_lock(&to_score->zq_mutex);
543 weights[i] = score_queue(to_score);
544 pthread_mutex_unlock(&to_score->zq_mutex);
545 if (weights[i] > NO_WORK)
546 queues_with_work++;
547 }
548 if (!queues_with_work) {
549 pthread_cond_wait(&pool.tp_wake_worker,
550 &pool.tp_pool_mutex);
551 } else {
552 int q = select_stochastic(weights, num_queues);
553 *queue = pool.tp_queues[q];
554 pthread_mutex_lock(&(*queue)->zq_mutex);
555 int count = claim_batch(*queue, batch);
556 pthread_mutex_unlock(&(*queue)->zq_mutex);
557 /*
558 * Try to wake up another worker thread if there
559 * still seems to be work available (on any queue).
560 */
561 if (atomic_load_64(&pool.tp_unclaimed) > 0) {
562 pthread_cond_signal(&pool.tp_wake_worker);
563 }
564 pthread_mutex_unlock(&pool.tp_pool_mutex);
565 return (count);
566 }
567 }
568 }
569
570 /*
571 * Batches are processed without holding any locks. The existence of the
572 * items we're working on guarantees that the queue can't be destroyed out
573 * from under us.
574 *
575 * However, we can't mark items completed without holding the queue lock
576 * because that creates a potential race condition with advance_indexes()
577 * being called on another thread.
578 */
579 static void *
queue_worker(void * dummy)580 queue_worker(void *dummy)
581 {
582 (void) dummy;
583 zstream_queue_t *queue;
584 queue_slot_t *batch[ZQ_MAX_BATCH];
585 int count;
586
587 while (B_TRUE) {
588 count = assign_queue_and_get_work(&queue, batch);
589 if (count) {
590 zq_process_item_f *process =
591 queue->zq_params.qp_process;
592 void *context = queue->zq_params.qp_context;
593 for (int i = 0; i < count; i++) {
594 process(batch[i]->qs_item, context);
595 }
596 pthread_mutex_lock(&queue->zq_mutex);
597 for (int i = 0; i < count; i++) {
598 batch[i]->qs_completed = B_TRUE;
599 }
600 advance_indexes(queue);
601 pthread_mutex_unlock(&queue->zq_mutex);
602 }
603 }
604 return (NULL);
605 }
606
607 /*
608 * Locking: must be called with the dispatch mutex held
609 *
610 * Skips the wakeup if tp_unclaimed == 0.
611 */
612 static inline void
maybe_wake_worker(void)613 maybe_wake_worker(void)
614 {
615 pool.tp_dispatch_requested = B_FALSE;
616 if (atomic_load_64(&pool.tp_unclaimed) > 0) {
617 pthread_mutex_lock(&pool.tp_pool_mutex);
618 pthread_cond_signal(&pool.tp_wake_worker);
619 pthread_mutex_unlock(&pool.tp_pool_mutex);
620 }
621 }
622
623 static inline struct timespec
timeout_timespec(void)624 timeout_timespec(void)
625 {
626 struct timespec expire;
627 struct timeval tv;
628
629 if (gettimeofday(&tv, NULL) != 0)
630 err(1, "couldn't gettimeofday()");
631 uint64_t nsec = tv.tv_usec * 1000 + DISPATCH_BACKUP_NSEC;
632 expire.tv_sec = tv.tv_sec + nsec / NANOSEC;
633 expire.tv_nsec = nsec % NANOSEC;
634 return (expire);
635 }
636
637 /*
638 * The enqueue notification pacing thread, which converts a notification
639 * from an enqueuer into a possible worker wakeup roughly ENQUEUE_DELAY_NSEC
640 * later. The delay facilitates larger batch sizes and keeps enqueuers on a
641 * less-contested mutex.
642 *
643 * The condwait timeout is necessary because the tp_unclaimed count is not
644 * the final word on whether there is actually any work to claim. It is
645 * calculated rigorously. However, it's a bare atomic and therefore
646 * potentially out of date at any given moment. A backup strategy is
647 * necessary to restart processing in the event of a race.
648 */
649 static void *
dispatch_worker(void * nope)650 dispatch_worker(void *nope)
651 {
652 (void) nope;
653 pthread_mutex_lock(&pool.tp_dispatch_mutex);
654 while (B_TRUE) {
655 while (!pool.tp_dispatch_requested) {
656 int rc;
657 struct timespec expire = timeout_timespec();
658 rc = pthread_cond_timedwait(&pool.tp_request_dispatch,
659 &pool.tp_dispatch_mutex, &expire);
660 if (rc == ETIMEDOUT) {
661 maybe_wake_worker();
662 } else if (rc != 0) {
663 errx(1, "pthread_cond_timedwait() failed: %s",
664 strerror(rc));
665 }
666 }
667 pthread_mutex_unlock(&pool.tp_dispatch_mutex);
668 sleep_nsec(ENQUEUE_DELAY_NSEC);
669 pthread_mutex_lock(&pool.tp_dispatch_mutex);
670 maybe_wake_worker();
671 }
672 return (NULL);
673 }
674
675 /*
676 * Implements both _enqueue and _fini. item == NULL for fini.
677 */
678 void
zstream_enqueue(zstream_queue_t * queue,queue_item_t * item)679 zstream_enqueue(zstream_queue_t *queue, queue_item_t *item)
680 {
681 VERIFY3P(queue, !=, NULL);
682 pthread_mutex_lock(&queue->zq_mutex);
683
684 VERIFY3B(queue->zq_disallow_enqueue, ==, B_FALSE);
685 while (Q_FULL(queue)) {
686 pthread_cond_wait(&queue->zq_cond.dequeued, &queue->zq_mutex);
687 }
688 VERIFY3B(queue->zq_disallow_enqueue, ==, B_FALSE);
689 queue_slot_t *slot = &Q_SLOT(queue, queue->zq_ix.enqueue);
690 if (item) {
691 slot->qs_cost =
692 queue->zq_params.qp_cost(item, queue->zq_params.qp_context);
693 slot->qs_completed = slot->qs_cost == 0;
694 slot->qs_end_of_stream = B_FALSE;
695 memcpy(slot->qs_item, item, queue->zq_params.qp_item_size);
696 } else {
697 slot->qs_cost = 0;
698 slot->qs_completed = B_TRUE;
699 slot->qs_end_of_stream = B_TRUE;
700 queue->zq_disallow_enqueue = B_TRUE;
701 }
702 queue->zq_ix.enqueue++;
703 atomic_inc_64(&pool.tp_unclaimed);
704 if (slot->qs_cost == 0)
705 advance_indexes(queue);
706
707 #ifdef MONITOR_QUEUES
708 /* Maintain queue usage data per monitor interval */
709 uint64_t depth = queue->zq_ix.enqueue - queue->zq_ix.dequeue;
710 queue->zq_stats.max_depth = MAX(queue->zq_stats.max_depth, depth);
711 queue->zq_stats.min_depth = MIN(queue->zq_stats.min_depth, depth);
712 #endif
713
714 pthread_mutex_unlock(&queue->zq_mutex);
715
716 pthread_mutex_lock(&pool.tp_dispatch_mutex);
717 pool.tp_dispatch_requested = B_TRUE;
718 pthread_cond_signal(&pool.tp_request_dispatch);
719 pthread_mutex_unlock(&pool.tp_dispatch_mutex);
720 }
721
722 void
zstream_queue_fini(zstream_queue_t * queue)723 zstream_queue_fini(zstream_queue_t *queue)
724 {
725 zstream_enqueue(queue, NULL);
726 }
727
728 /*
729 * This function is not public. The only way to destroy a queue through the
730 * public API is to call zstream_queue_fini(), wait for all items to be
731 * processed, and then dequeue all items. As a consequence, threads are
732 * entitled to assume that any queue with unprocessed work will not be
733 * removed without locking the pool mutex.
734 *
735 * Locking: the caller must NOT hold the queue lock. The pool mutex is held
736 * while destroying the queue.
737 */
738 static void
zstream_queue_destroy(zstream_queue_t * queue)739 zstream_queue_destroy(zstream_queue_t *queue)
740 {
741 pthread_mutex_lock(&pool.tp_pool_mutex);
742
743 #ifdef MONITOR_QUEUES
744 print_batch_size_histogram(queue);
745 #endif
746
747 VERIFY0(pthread_mutex_destroy(&queue->zq_mutex));
748 VERIFY0(pthread_cond_destroy(&queue->zq_cond.dequeued));
749 if (pthread_cond_destroy(&queue->zq_cond.completed) != 0) {
750 errx(1, "cannot destroy zstream_queue completed condition - "
751 "are you attempting to dequeue from multiple threads "
752 "simultaneously?");
753 }
754 pool.tp_num_queues--;
755 if (pool.tp_num_queues > 0) {
756 /* Gaps are not allowed in the tp_queues array */
757 zstream_queue_t **qscan = &pool.tp_queues[0];
758 int i = pool.tp_num_queues;
759 while (*qscan != queue) { qscan++; i--; }
760 if (i > 0)
761 memmove(qscan, qscan + 1, i * sizeof (*qscan));
762 }
763 /*
764 * Items are allocated as a single block. The address of the first
765 * item field is in fact the start of the block.
766 */
767 free(queue->zq_slots[0].qs_item);
768 free(queue->zq_slots);
769 queue->zq_slots = NULL;
770 free(queue);
771
772 pthread_mutex_unlock(&pool.tp_pool_mutex);
773 }
774
775 /*
776 * Locking: if more than one thread attempts to dequeue items
777 * simultaneously, disaster is likely. It will work fine until the end of
778 * the stream, at which point it becomes a tossup between a race condition
779 * with multiple attempts to destroy the whole queue vs. an attempt to
780 * delete a condition that another thread is waiting on. Hence the warning
781 * not to do multithreaded dequeues in zstream_queue.h.
782 *
783 * Returns B_TRUE if real data is returned, B_FALSE if the end of the queue
784 * has been reached.
785 */
786 boolean_t
zstream_dequeue(zstream_queue_t * queue,queue_item_t * item)787 zstream_dequeue(zstream_queue_t *queue, queue_item_t *item)
788 {
789 pthread_mutex_lock(&queue->zq_mutex);
790 while (queue->zq_ix.dequeue >= queue->zq_ix.complete) {
791 pthread_cond_wait(&queue->zq_cond.completed, &queue->zq_mutex);
792 }
793 queue_slot_t *slot = &Q_SLOT(queue, queue->zq_ix.dequeue);
794 queue->zq_ix.dequeue++;
795 if (slot->qs_end_of_stream) {
796 pthread_mutex_unlock(&queue->zq_mutex);
797 /* Potential multi-dequeuer race point */
798 zstream_queue_destroy(queue);
799 return (B_FALSE);
800 } else {
801 memcpy(item, slot->qs_item, queue->zq_params.qp_item_size);
802 pthread_cond_signal(&queue->zq_cond.dequeued);
803 pthread_mutex_unlock(&queue->zq_mutex);
804 return (B_TRUE);
805 }
806 }
807
808 #ifdef MONITOR_QUEUES
809
810 #define USEC_PER_JIFFY 10000
811 #define SAMPLE_DURATION_USEC 1000000
812 #define CPU_FIELD_WIDTH 14
813
814 /*
815 * Called only during zstream_queue_destroy(), under the pool mutex
816 */
817 static void
print_batch_size_histogram(zstream_queue_t * queue)818 print_batch_size_histogram(zstream_queue_t *queue)
819 {
820 int last_nonzero = 0;
821 static int lines_printed = 0;
822
823 if (lines_printed++ == 0)
824 fprintf(stderr, "\nBatch size histograms:\n");
825 for (last_nonzero = ZQ_MAX_BATCH; last_nonzero >= 0; last_nonzero--) {
826 if (queue->zq_histogram[last_nonzero] > 0)
827 break;
828 }
829 fprintf(stderr, "Queue %d: ", queue->zq_id);
830 const char *sep = "";
831 for (int i = 0; i <= last_nonzero; i++) {
832 fprintf(stderr, "%s%llu", sep,
833 (u_longlong_t)queue->zq_histogram[i]);
834 sep = ", ";
835 }
836 fprintf(stderr, "\n");
837 fflush(stderr);
838 }
839
840 /*
841 * Monitor queue and CPU usage from a separate thread. This is all
842 * Linux-specific, but it's needed only while tuning queue lengths and
843 * batch sizes. Prints the minimum and maximum queue depth observed
844 * during each period.
845 *
846 * Example output:
847 *
848 * CPU: 99.85% Queue 0: 745-1024 Queue 1: 183-256
849 */
850 static void *
cpu_and_queue_monitor(void * dummy)851 cpu_and_queue_monitor(void *dummy)
852 {
853 (void) dummy;
854 uint64_t period = SAMPLE_DURATION_USEC;
855 struct timespec clock = {0};
856 uint64_t start_us, end_us;
857 uint64_t cpu_jif_prior = 0;
858 uint64_t delta_jif, delta_cpu_jif;
859 long unsigned int utime, stime;
860 char buff[1024];
861 FILE *fp;
862
863 fprintf(stderr, "Queue depths:\n");
864
865 while (B_TRUE) {
866
867 usleep(period);
868
869 fp = fopen("/proc/self/stat", "r");
870 VERIFY3P(fp, !=, NULL);
871 VERIFY3P(fgets(buff, sizeof (buff), fp), !=, NULL);
872 fclose(fp);
873 char *p = strrchr(buff, ')');
874 VERIFY3P(p, !=, NULL);
875 p += 2; /* skip ") " and fields 3-13 */
876 for (int i = 0; i < 11; i++) {
877 p = strchr(p, ' ');
878 VERIFY3P(p, !=, NULL);
879 p++;
880 }
881 VERIFY3U(sscanf(p, "%lu %lu", &utime, &stime), ==, 2);
882
883 pthread_mutex_lock(&pool.tp_pool_mutex);
884
885 clock_gettime(CLOCK_MONOTONIC, &clock);
886 end_us = clock.tv_sec * 1000000 + clock.tv_nsec / 1000;
887
888 if (cpu_jif_prior > 0) {
889 delta_cpu_jif = utime + stime - cpu_jif_prior;
890 delta_jif = (end_us - start_us) / USEC_PER_JIFFY;
891 double cpu_pct = (double)delta_cpu_jif /
892 (pool.tp_num_threads * delta_jif);
893 cpu_pct = MIN(cpu_pct, 0.9999); /* Don't print 100% */
894 fprintf(stderr, "CPU: %5.2f%% ", 100 * cpu_pct);
895 } else {
896 /* No CPU data available for the first interval */
897 fprintf(stderr, "%*s", CPU_FIELD_WIDTH, "");
898 }
899
900 for (int i = 0; i < pool.tp_num_queues; i++) {
901 zstream_queue_t *q = pool.tp_queues[i];
902 pthread_mutex_lock(&q->zq_mutex);
903 int min = q->zq_stats.min_depth;
904 int max = q->zq_stats.max_depth;
905 if (min > max)
906 min = max = 0;
907 fprintf(stderr, "Queue %d: %4d-%-4d ",
908 q->zq_id, min, max);
909 q->zq_stats.min_depth = INT_MAX;
910 q->zq_stats.max_depth = 0;
911 pthread_mutex_unlock(&q->zq_mutex);
912 }
913
914 pthread_mutex_unlock(&pool.tp_pool_mutex);
915
916 fprintf(stderr, "\n");
917 fflush(stderr);
918
919 cpu_jif_prior = utime + stime;
920 start_us = end_us;
921 }
922 return (NULL);
923 }
924
925 #endif /* MONITOR_QUEUES */
926