Asterisk - The Open Source Telephony Project GIT-master-70eff7f
Loading...
Searching...
No Matches
taskpool.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2025, Sangoma Technologies Corporation
5 *
6 * Joshua Colp <jcolp@sangoma.com>
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19
20#include "asterisk.h"
21
22#include "asterisk/_private.h"
23#include "asterisk/taskpool.h"
25#include "asterisk/astobj2.h"
27#include "asterisk/utils.h"
28#include "asterisk/time.h"
29#include "asterisk/sched.h"
30
31/*!
32 * \brief A taskpool taskprocessor
33 */
35 /*! The underlying taskprocessor */
37 /*! The last time a task was pushed to this taskprocessor */
38 struct timeval last_pushed;
39};
40
41/*!
42 * \brief A container of taskprocessors
43 */
45 /*! A vector of taskprocessors */
47 /*! The next taskprocessor to use for pushing */
48 unsigned int taskprocessor_num;
49};
50
52 struct taskpool_taskprocessor **taskprocessor, unsigned int *growth_threshold_reached);
53
54/*!
55 * \brief An opaque taskpool structure
56 *
57 * A taskpool is a collection of taskprocessors that
58 * execute tasks, each from their own queue. A selector
59 * determines which taskprocessor to queue to at push
60 * time.
61 */
63 /*! The static taskprocessors, those which will always exist */
65 /*! The dynamic taskprocessors, those which will be created as needed */
67 /*! True if the taskpool is in the process of shutting down */
69 /*! Taskpool-specific options */
71 /*! Dynamic pool shrinking scheduled item */
73 /*! The taskprocessor selector to use */
75 /*! The name of the taskpool */
76 char name[0];
77};
78
79/*! \brief The threshold for a taskprocessor at which we consider the pool needing to grow (50% of high water threshold) */
80#define TASKPOOL_GROW_THRESHOLD (AST_TASKPROCESSOR_HIGH_WATER_LEVEL * 5) / 10
81
82/*!
83 * \internal
84 * \brief Effective load of a pool taskprocessor
85 *
86 * The queue size alone does not account for the task currently being executed,
87 * so a taskprocessor inside a long running task reports the same load as an
88 * idle one.
89 */
95
96/*! \brief Scheduler used for dynamic pool shrinking */
97static struct ast_sched_context *sched;
98
99/*! \brief Thread storage for the current taskpool */
100AST_THREADSTORAGE_RAW(current_taskpool_pool);
101
102/*!
103 * \internal
104 * \brief Get the current taskpool associated with this thread.
105 */
107{
108 return ast_threadstorage_get_ptr(&current_taskpool_pool);
109}
110
111/*!
112 * \internal
113 * \brief Shutdown task for taskpool taskprocessor
114 */
115 static int taskpool_taskprocessor_stop(void *data)
116 {
117 struct ast_taskpool *pool = ast_taskpool_get_current();
118
119 /* If a thread stop callback is set on the options, call it */
120 if (pool->options.thread_end) {
121 pool->options.thread_end();
122 }
123
124 ao2_cleanup(pool);
125
126 return 0;
127 }
128
129/*! \internal */
130static void taskpool_taskprocessor_dtor(void *obj)
131{
133
135 /* We can't actually do anything if this fails, so just accept reality */
136 }
137
139}
140
141/*!
142 * \internal
143 * \brief Startup task for taskpool taskprocessor
144 */
145static int taskpool_taskprocessor_start(void *data)
146{
147 struct ast_taskpool *pool = data;
148
149 /* Set the pool on the thread for this taskprocessor, inheriting the
150 * reference passed to the task itself.
151 */
152 ast_threadstorage_set_ptr(&current_taskpool_pool, pool);
153
154 /* If a thread start callback is set on the options, call it */
155 if (pool->options.thread_start) {
156 pool->options.thread_start();
157 }
158
159 return 0;
160}
161
162/*!
163 * \internal
164 * \brief Allocate a taskpool specific taskprocessor
165 */
167{
169 char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
170
171 /* We don't actually need locking for each pool taskprocessor, as the only thing
172 * mutable is the underlying taskprocessor which has its own internal locking.
173 */
175 if (!taskprocessor) {
176 return NULL;
177 }
178
179 /* Create name with seq number appended. */
180 ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "taskpool/%c:%s", type, pool->name);
181
182 taskprocessor->taskprocessor = ast_taskprocessor_get(tps_name, TPS_REF_DEFAULT);
183 if (!taskprocessor->taskprocessor) {
185 return NULL;
186 }
187
188 taskprocessor->last_pushed = ast_tvnow();
189
191 ao2_ref(pool, -1);
192 /* Prevent the taskprocessor from queueing the stop task by explicitly unreferencing and setting it to
193 * NULL here.
194 */
196 taskprocessor->taskprocessor = NULL;
197 return NULL;
198 }
199
200 return taskprocessor;
201}
202
203/*!
204 * \internal
205 * \brief Initialize the taskpool taskprocessors structure
206 */
207static int taskpool_taskprocessors_init(struct taskpool_taskprocessors *taskprocessors, unsigned int size)
208{
209 if (AST_VECTOR_INIT(&taskprocessors->taskprocessors, size)) {
210 return -1;
211 }
212
213 return 0;
214}
215
216/*!
217 * \internal
218 * \brief Clean up the taskpool taskprocessors structure
219 */
221{
222 /* Access/manipulation of taskprocessors is done with the lock held, and
223 * with a check of the shutdown flag done. This means that outside of holding
224 * the lock we can safely muck with it. Pushing to the taskprocessor is done
225 * outside of the lock, but with a reference to the taskprocessor held.
226 */
228 AST_VECTOR_FREE(&taskprocessors->taskprocessors);
229}
230
231/*!
232 * \internal
233 * \brief Determine if a taskpool taskprocessor is idle
234 */
235#define TASKPROCESSOR_IS_IDLE(tps, timeout) (ast_tvdiff_ms(ast_tvnow(), tps->last_pushed) > (timeout))
236
237/*! \internal
238 * \brief Taskpool dynamic pool shrink function
239 */
240static int taskpool_dynamic_pool_shrink(const void *data)
241{
242 struct ast_taskpool *pool = (struct ast_taskpool *)data;
243 int num_removed;
244
245 ao2_lock(pool);
246
247 /* If the pool is shutting down, do nothing and don't reschedule */
248 if (pool->shutting_down) {
249 ao2_unlock(pool);
250 ao2_ref(pool, -1);
251 return 0;
252 }
253
254 /* Go through the dynamic taskprocessors and find any which have been idle long enough and remove them */
257 if (num_removed) {
258 /* If we've removed any taskprocessors the taskprocessor_num may no longer be valid, so update it */
261 }
262 }
263
264 ao2_unlock(pool);
265
266 /* It is possible for the pool to have been shut down between unlocking and returning, this is
267 * inherently a race condition we can't eliminate so we will catch it on the next iteration.
268 */
269 return pool->options.idle_timeout * 1000;
270}
271
272/*!
273 * \internal
274 * \brief Sequential taskprocessor selector
275 */
276 static void taskpool_sequential_selector(struct ast_taskpool *pool, struct taskpool_taskprocessors *taskprocessors,
277 struct taskpool_taskprocessor **taskprocessor, unsigned int *growth_threshold_reached)
278{
279 unsigned int taskprocessor_num = taskprocessors->taskprocessor_num;
280
281 if (!AST_VECTOR_SIZE(&taskprocessors->taskprocessors)) {
282 *growth_threshold_reached = 1;
283 return;
284 }
285
286 taskprocessors->taskprocessor_num++;
287 if (taskprocessors->taskprocessor_num == AST_VECTOR_SIZE(&taskprocessors->taskprocessors)) {
288 taskprocessors->taskprocessor_num = 0;
289 }
290
291 *taskprocessor = AST_VECTOR_GET(&taskprocessors->taskprocessors, taskprocessor_num);
292
293 /* Check to see if this has reached the growth threshold */
294 *growth_threshold_reached = (taskpool_taskprocessor_load(*taskprocessor) >= pool->options.growth_threshold) ? 1 : 0;
295}
296
297/*!
298 * \interal
299 * \brief Least full taskprocessor selector
300 */
301static void taskpool_least_full_selector(struct ast_taskpool *pool, struct taskpool_taskprocessors *taskprocessors,
302 struct taskpool_taskprocessor **taskprocessor, unsigned int *growth_threshold_reached)
303{
304 struct taskpool_taskprocessor *least_full = NULL;
305 long least_full_load = 0;
306 unsigned int i;
307
308 if (!AST_VECTOR_SIZE(&taskprocessors->taskprocessors)) {
309 *growth_threshold_reached = 1;
310 return;
311 }
312
313 /* We assume that the growth threshold has not yet been reached, until proven otherwise */
314 *growth_threshold_reached = 0;
315
316 for (i = 0; i < AST_VECTOR_SIZE(&taskprocessors->taskprocessors); i++) {
317 struct taskpool_taskprocessor *tp = AST_VECTOR_GET(&taskprocessors->taskprocessors, i);
318 long load = taskpool_taskprocessor_load(tp);
319
320 /* If this taskprocessor has nothing queued and nothing in flight, it is the best choice */
321 if (!load) {
322 *taskprocessor = tp;
323 return;
324 }
325
326 /* If any of the taskprocessors have reached the growth threshold then we should grow the pool */
327 if (load >= pool->options.growth_threshold) {
328 *growth_threshold_reached = 1;
329 }
330
331 /* The taskprocessor with the lowest load should be used */
332 if (!least_full || load < least_full_load) {
333 least_full = tp;
334 least_full_load = load;
335 }
336 }
337
338 *taskprocessor = least_full;
339}
340
342 const struct ast_taskpool_options *options)
343{
344 struct ast_taskpool *pool;
345
346 /* Enforce versioning on the passed-in options */
347 if (options->version != AST_TASKPOOL_OPTIONS_VERSION) {
348 return NULL;
349 }
350
351 pool = ao2_alloc(sizeof(*pool) + strlen(name) + 1, NULL);
352 if (!pool) {
353 return NULL;
354 }
355
356 strcpy(pool->name, name); /* Safe */
357 memcpy(&pool->options, options, sizeof(pool->options));
358 pool->shrink_sched_id = -1;
359
360 /* Verify the passed-in options are valid, and adjust if needed */
361 if (options->initial_size < options->minimum_size) {
362 pool->options.initial_size = options->minimum_size;
363 ast_log(LOG_WARNING, "Taskpool '%s' has an initial size of %d, which is less than the minimum size of %d. Adjusting to %d.\n",
364 name, options->initial_size, options->minimum_size, options->minimum_size);
365 }
366
367 if (options->max_size && pool->options.initial_size > options->max_size) {
368 pool->options.max_size = pool->options.initial_size;
369 ast_log(LOG_WARNING, "Taskpool '%s' has a max size of %d, which is less than the initial size of %d. Adjusting to %d.\n",
370 name, options->max_size, pool->options.initial_size, pool->options.initial_size);
371 }
372
373 if (!options->auto_increment) {
374 if (!pool->options.minimum_size) {
375 pool->options.minimum_size = 1;
376 ast_log(LOG_WARNING, "Taskpool '%s' has a minimum size of 0, which is not valid without auto increment. Adjusting to 1.\n", name);
377 }
378 if (!pool->options.max_size) {
379 pool->options.max_size = pool->options.minimum_size;
380 ast_log(LOG_WARNING, "Taskpool '%s' has a max size of 0, which is not valid without auto increment. Adjusting to %d.\n", name, pool->options.minimum_size);
381 }
382 if (pool->options.minimum_size != pool->options.max_size) {
383 pool->options.minimum_size = pool->options.max_size;
384 pool->options.initial_size = pool->options.max_size;
385 ast_log(LOG_WARNING, "Taskpool '%s' has a minimum size of %d, while max size is %d. Adjusting all sizes to %d due to lack of auto increment.\n",
386 name, options->minimum_size, pool->options.max_size, pool->options.max_size);
387 }
388 } else if (!options->growth_threshold) {
390 }
391
394 } else if (options->selector == AST_TASKPOOL_SELECTOR_SEQUENTIAL) {
396 } else {
397 ast_log(LOG_WARNING, "Taskpool '%s' has an invalid selector of %d. Adjusting to default selector.\n",
398 name, options->selector);
400 }
401
403 ao2_ref(pool, -1);
404 return NULL;
405 }
406
407 /* Create the static taskprocessors based on the passed-in options */
408 for (int i = 0; i < pool->options.minimum_size; i++) {
410
412 if (!taskprocessor) {
413 /* The reference to pool is passed to ast_taskpool_shutdown */
415 return NULL;
416 }
417
420 /* The reference to pool is passed to ast_taskpool_shutdown */
422 return NULL;
423 }
424 }
425
427 pool->options.initial_size - pool->options.minimum_size)) {
429 return NULL;
430 }
431
432 /* Create the dynamic taskprocessor based on the passed-in options */
433 for (int i = 0; i < (pool->options.initial_size - pool->options.minimum_size); i++) {
435
437 if (!taskprocessor) {
438 /* The reference to pool is passed to ast_taskpool_shutdown */
440 return NULL;
441 }
442
445 /* The reference to pool is passed to ast_taskpool_shutdown */
447 return NULL;
448 }
449 }
450
451 /* If idle timeout support is enabled kick off a scheduled task to shrink the dynamic pool periodically, we do
452 * this no matter if there are dynamic taskprocessor present to reduce the work needed within the push function
453 * and to reduce complexity.
454 */
455 if (options->idle_timeout && options->auto_increment) {
457 if (pool->shrink_sched_id < 0) {
458 ao2_ref(pool, -1);
459 /* The second reference to pool is passed to ast_taskpool_shutdown */
461 return NULL;
462 }
463 }
464
465 return pool;
466}
467
469{
470 size_t count;
471
472 ao2_lock(pool);
474 ao2_unlock(pool);
475
476 return count;
477}
478
479#define TASKPOOL_QUEUE_SIZE_ADD(tps, size) (size += ast_taskprocessor_size(tps->taskprocessor))
480
482{
483 long queue_size = 0;
484
485 ao2_lock(pool);
488 ao2_unlock(pool);
489
490 return queue_size;
491}
492
493/*! \internal
494 * \brief Taskpool dynamic pool grow function
495 */
497{
498 unsigned int num_to_add = pool->options.auto_increment;
499 int i;
500
501 if (!num_to_add) {
502 return;
503 }
504
505 /* If a maximum size is enforced, then determine if we have to limit how many taskprocessors we add */
506 if (pool->options.max_size) {
508
509 if (current_size + num_to_add > pool->options.max_size) {
510 num_to_add = pool->options.max_size - current_size;
511 }
512 }
513
514 for (i = 0; i < num_to_add; i++) {
515 struct taskpool_taskprocessor *new_taskprocessor;
516
517 new_taskprocessor = taskpool_taskprocessor_alloc(pool, 'd');
518 if (!new_taskprocessor) {
519 return;
520 }
521
522 if (AST_VECTOR_APPEND(&pool->dynamic_taskprocessors.taskprocessors, new_taskprocessor)) {
523 ao2_ref(new_taskprocessor, -1);
524 return;
525 }
526
527 if (i == 0) {
528 /* On the first iteration we return the taskprocessor we just added */
529 *taskprocessor = new_taskprocessor;
530 /* We assume we will be going back to the first taskprocessor, since we are at the end of the vector */
532 } else if (i == 1) {
533 /* On the second iteration we update the next taskprocessor to use to be this one */
535 }
536 }
537}
538
539#undef ast_taskpool_push
540#define ast_taskpool_push_internal(pool, task, data) \
541 __ast_taskpool_push(pool, task, data, __FILE__, __LINE__, __PRETTY_FUNCTION__)
542int ast_taskpool_push(struct ast_taskpool *pool, int (*task)(void *data), void *data);
543
544int __ast_taskpool_push(struct ast_taskpool *pool, int (*task)(void *data), void *data,
545 const char *file, int line, const char *function)
546{
548
549 /* Select the taskprocessor in the pool to use for pushing this task */
550 ao2_lock(pool);
551 if (!pool->shutting_down) {
552 unsigned int growth_threshold_reached = 0;
553
554 /* A selector doesn't set taskprocessor to NULL, it will only change the value if a better
555 * taskprocessor is found. This means that even if the selector for a dynamic taskprocessor
556 * fails for some reason, it will still fall back to the initially found static one if
557 * it is present.
558 */
559 pool->selector(pool, &pool->static_taskprocessors, &taskprocessor, &growth_threshold_reached);
560 if (pool->options.auto_increment && growth_threshold_reached) {
561 /* If we need to grow then try dynamic taskprocessors */
562 pool->selector(pool, &pool->dynamic_taskprocessors, &taskprocessor, &growth_threshold_reached);
563 if (growth_threshold_reached) {
564 /* If we STILL need to grow then grow the dynamic taskprocessor pool if allowed */
566 }
567
568 /* If a dynamic taskprocessor was used update its last push time */
569 if (taskprocessor) {
570 taskprocessor->last_pushed = ast_tvnow();
571 }
572 }
574 }
575 ao2_unlock(pool);
576
577 if (!taskprocessor) {
578 return -1;
579 }
580
581 if (__ast_taskprocessor_push(taskprocessor->taskprocessor, task, data, file, line, function)) {
582 return -1;
583 }
584
585 return 0;
586}
587
588/* ABI compatibility: Provide actual function symbol for external modules */
589int ast_taskpool_push(struct ast_taskpool *pool, int (*task)(void *data), void *data)
590{
591 return __ast_taskpool_push(pool, task, data, NULL, 0, NULL);
592}
593
594/*!
595 * \internal Structure used for synchronous task
596 */
605
606/*!
607 * \internal Initialization function for synchronous task
608 */
609static int taskpool_sync_task_init(struct taskpool_sync_task *sync_task, int (*task)(void *), void *data)
610{
611 ast_mutex_init(&sync_task->lock);
612 ast_cond_init(&sync_task->cond, NULL);
613 sync_task->complete = 0;
614 sync_task->fail = 0;
615 sync_task->task = task;
616 sync_task->task_data = data;
617 return 0;
618}
619
620/*!
621 * \internal Cleanup function for synchronous task
622 */
623static void taskpool_sync_task_cleanup(struct taskpool_sync_task *sync_task)
624{
625 ast_mutex_destroy(&sync_task->lock);
626 ast_cond_destroy(&sync_task->cond);
627}
628
629/*!
630 * \internal Function for executing a sychronous task
631 */
632static int taskpool_sync_task(void *data)
633{
634 struct taskpool_sync_task *sync_task = data;
635 int ret;
636
637 sync_task->fail = sync_task->task(sync_task->task_data);
638
639 /*
640 * Once we unlock sync_task->lock after signaling, we cannot access
641 * sync_task again. The thread waiting within ast_taskpool_push_wait()
642 * is free to continue and release its local variable (sync_task).
643 */
644 ast_mutex_lock(&sync_task->lock);
645 sync_task->complete = 1;
646 ast_cond_signal(&sync_task->cond);
647 ret = sync_task->fail;
648 ast_mutex_unlock(&sync_task->lock);
649 return ret;
650}
651
652int __ast_taskpool_push_wait(struct ast_taskpool *pool, int (*task)(void *data), void *data,
653 const char *file, int line, const char *function)
654{
655 struct taskpool_sync_task sync_task;
656
657 /* If we are already executing within a taskpool taskprocessor then
658 * don't bother pushing a new task, just directly execute the task.
659 */
661 return task(data);
662 }
663
664 if (taskpool_sync_task_init(&sync_task, task, data)) {
665 return -1;
666 }
667
668 if (__ast_taskpool_push(pool, taskpool_sync_task, &sync_task, file, line, function)) {
669 taskpool_sync_task_cleanup(&sync_task);
670 return -1;
671 }
672
673 ast_mutex_lock(&sync_task.lock);
674 while (!sync_task.complete) {
675 ast_cond_wait(&sync_task.cond, &sync_task.lock);
676 }
677 ast_mutex_unlock(&sync_task.lock);
678
679 taskpool_sync_task_cleanup(&sync_task);
680 return sync_task.fail;
681}
682
683/* ABI compatibility: Provide actual function symbol for external modules */
684#undef ast_taskpool_push_wait
685int ast_taskpool_push_wait(struct ast_taskpool *pool, int (*task)(void *data), void *data);
686
687int ast_taskpool_push_wait(struct ast_taskpool *pool, int (*task)(void *data), void *data)
688{
689 return __ast_taskpool_push_wait(pool, task, data, NULL, 0, NULL);
690}
691
693{
694 if (!pool) {
695 return;
696 }
697
698 /* Mark this pool as shutting down so nothing new is pushed */
699 ao2_lock(pool);
700 pool->shutting_down = 1;
701 ao2_unlock(pool);
702
703 /* Stop the shrink scheduled item if present */
705
706 /* Clean up all the taskprocessors */
709
710 ao2_ref(pool, -1);
711}
712
714 SERIALIZER_UNSUSPENDED = 0, /* The serializer is unsuspended */
715 SERIALIZER_SUSPENDING, /* The serializer is pending suspension */
716 SERIALIZER_SUSPENDED, /* The serializer is suspended */
717};
718
720 /*! Taskpool the serializer will use to process the jobs. */
722 /*! Which group will wait for this serializer to shutdown. */
724 /*! Condition for synchronization during suspension. */
726 /*! Serializer suspension status. */
728};
729
730static void serializer_dtor(void *obj)
731{
732 struct serializer *ser = obj;
733
734 ao2_cleanup(ser->pool);
736 ast_cond_destroy(&ser->cond);
737}
738
741{
742 struct serializer *ser;
743
744 /* This object has a lock so it can be used to ensure exclusive access
745 * to the execution of tasks within the serializer.
746 */
747 ser = ao2_alloc(sizeof(*ser), serializer_dtor);
748 if (!ser) {
749 return NULL;
750 }
751 ser->pool = ao2_bump(pool);
753 ast_cond_init(&ser->cond, NULL);
754 return ser;
755}
756
757AST_THREADSTORAGE_RAW(current_taskpool_serializer);
758
759static int execute_tasks(void *data)
760{
761 struct ast_taskpool *pool = ast_taskpool_get_current();
762 struct ast_taskprocessor *tps = data;
765 size_t remaining, requeue = 0;
766
767 /* In a normal scenario this lock will not be in contention with
768 * anything else. It is only if a synchronous task is pushed to
769 * the serializer that it may be blocked on the synchronous
770 * task thread. This is done to ensure that only one thread is executing
771 * tasks from the serializer at a given time, and not out of order
772 * either.
773 */
774 ao2_lock(ser);
775
776 ast_threadstorage_set_ptr(&current_taskpool_serializer, tps);
777 for (remaining = ast_taskprocessor_size(tps); remaining > 0; remaining--) {
778 requeue = ast_taskprocessor_execute(tps);
779
780 /* If the serializer is suspended we will not execute any more tasks and
781 * we will not requeue the taskpool task. Instead it will be requeued when
782 * the serializer is unsuspended.
783 */
784 if (ser->suspended == SERIALIZER_SUSPENDED) {
785 requeue = 0;
786 break;
787 }
788 }
789 ast_threadstorage_set_ptr(&current_taskpool_serializer, NULL);
790
791 ao2_unlock(ser);
792
793 /* If there are remaining tasks we requeue, this way the serializer
794 * does not hold exclusivity of the taskpool taskprocessor
795 */
796 if (requeue) {
797 /* Ownership passes to the new task */
800 }
801 } else {
803 }
804
805 return 0;
806}
807
809{
810 if (was_empty) {
813
814 if (ast_taskpool_push(ser->pool, execute_tasks, tps)) {
816 }
817 }
818}
819
821{
822 /* No-op */
823 return 0;
824}
825
835
841
843{
844 return ast_threadstorage_get_ptr(&current_taskpool_serializer);
845}
846
849{
850 struct serializer *ser;
852 struct ast_taskprocessor *tps;
853
855 if (!ser) {
856 return NULL;
857 }
858
860 if (!listener) {
861 ao2_ref(ser, -1);
862 return NULL;
863 }
864
866 if (!tps) {
867 /* ser ref transferred to listener but not cleaned without tps */
868 ao2_ref(ser, -1);
869 } else if (shutdown_group) {
871 }
872
873 ao2_ref(listener, -1);
874 return tps;
875}
876
878{
880}
881
882/*!
883 * \internal An empty task callback, used to ensure the serializer does not
884 * go empty. */
885static int taskpool_serializer_empty_task(void *data)
886{
887 return 0;
888}
889
890/* ABI compatibility: Provide actual function symbol for external modules */
891#undef ast_taskpool_serializer_push_wait
892int ast_taskpool_serializer_push_wait(struct ast_taskprocessor *serializer, int (*task)(void *data), void *data);
893
894int ast_taskpool_serializer_push_wait(struct ast_taskprocessor *serializer, int (*task)(void *data), void *data)
895{
897}
898
899int __ast_taskpool_serializer_push_wait(struct ast_taskprocessor *serializer, int (*task)(void *data), void *data,
900 const char *file, int line, const char *function)
901{
904 struct ast_taskprocessor *prior_serializer;
905 struct taskpool_sync_task sync_task;
906
907 /* If not in a taskpool taskprocessor we can just queue the task like normal and
908 * wait. */
910 if (taskpool_sync_task_init(&sync_task, task, data)) {
911 return -1;
912 }
913
914 if (__ast_taskprocessor_push(serializer, taskpool_sync_task, &sync_task, file, line, function)) {
915 taskpool_sync_task_cleanup(&sync_task);
916 return -1;
917 }
918
919 ast_mutex_lock(&sync_task.lock);
920 while (!sync_task.complete) {
921 ast_cond_wait(&sync_task.cond, &sync_task.lock);
922 }
923 ast_mutex_unlock(&sync_task.lock);
924
925 taskpool_sync_task_cleanup(&sync_task);
926 return sync_task.fail;
927 }
928
929 /* It is possible that we are already executing within a serializer, so stash the existing
930 * away so we can restore it.
931 */
932 prior_serializer = ast_taskpool_serializer_get_current();
933
934 ao2_lock(ser);
935
936 /* There are two cases where we can or have to directly execute this task:
937 * 1. There are no other tasks in the serializer
938 * 2. We are already in the serializer
939 * In the second case if we don't execute the task now, we will deadlock waiting
940 * on it as it will never occur.
941 */
942 if (!ast_taskprocessor_size(serializer) || prior_serializer == serializer) {
943 ast_threadstorage_set_ptr(&current_taskpool_serializer, serializer);
944 sync_task.fail = task(data);
945 ao2_unlock(ser);
946 ast_threadstorage_set_ptr(&current_taskpool_serializer, prior_serializer);
947 return sync_task.fail;
948 }
949
950 if (taskpool_sync_task_init(&sync_task, task, data)) {
951 ao2_unlock(ser);
952 return -1;
953 }
954
955 /* First we queue the serialized task */
956 if (__ast_taskprocessor_push(serializer, taskpool_sync_task, &sync_task, file, line, function)) {
957 taskpool_sync_task_cleanup(&sync_task);
958 ao2_unlock(ser);
959 return -1;
960 }
961
962 /* Next we queue the empty task to ensure the serializer doesn't reach empty, this
963 * stops two tasks from being queued for the same serializer at the same time.
964 */
966 taskpool_sync_task_cleanup(&sync_task);
967 ao2_unlock(ser);
968 return -1;
969 }
970
971 /* Now we execute the tasks on the serializer until our sync task is complete */
972 ast_threadstorage_set_ptr(&current_taskpool_serializer, serializer);
973 while (!sync_task.complete) {
974 /* If the serializer is suspended wait until it unsuspends */
975 while (ser->suspended == SERIALIZER_SUSPENDED) {
977 }
978
979 /* The sync task is guaranteed to be executed, so doing a while loop on the complete
980 * flag is safe.
981 */
983 }
984 taskpool_sync_task_cleanup(&sync_task);
985 ao2_unlock(ser);
986
987 ast_threadstorage_set_ptr(&current_taskpool_serializer, prior_serializer);
988
989 return sync_task.fail;
990}
991
992/*!
993 * \internal A task that suspends the serializer after queuing an empty task
994 */
996{
997 struct ast_taskprocessor *serializer = data;
1000
1001 /* First we queue the empty task to ensure the serializer doesn't reach empty, this
1002 * prevents any threads from queueing up a taskpool task that executes the serializer
1003 * while it is suspended, allowing us to queue it ourselves when the serializer is
1004 * unsuspended.
1005 */
1007 return -1;
1008 }
1009
1010 /* Next we suspend the serializer so that the execute_tasks currently executing stops
1011 * and doesn't requeue.
1012 */
1014
1015 return 0;
1016}
1017
1019{
1022
1023 /* This suspension process works by inserting a checkpoint into the queue of the
1024 * serializer. Once this checkpoint is reached the taskpool taskprocessor handling
1025 * the queue stops prematurely and does not get requeued. For the case where a
1026 * synchronous task wait is in progress it is instead paused temporarily. Once
1027 * the serializer is unsuspended a new execution task is queued into the taskpool
1028 * to resume execution and any paused synchronous task waits are awoken to resume
1029 * their own execution as well. This approach minimizes the number of threads that
1030 * are paused waiting, nominally to 0.
1031 */
1032
1034 return -1;
1035 }
1036
1037 ao2_lock(ser);
1038
1039 /* If the serializer is already suspending or suspended, just return immediately.
1040 * This mirrors the original behavior from PJSIP.
1041 */
1042 if (ser->suspended != SERIALIZER_UNSUSPENDED) {
1043 ao2_unlock(ser);
1044 return 0;
1045 }
1046
1048
1049 ao2_unlock(ser);
1050
1051 /* Once this returns successfully there is no thread executing the tasks on the serializer,
1052 * so they will accumulate until the serializer is unsuspended.
1053 */
1055 /* Suspension failed, so unsuspend as doing otherwise would leave the serializer in a stuck
1056 * state.
1057 */
1058 ao2_lock(ser);
1060 ao2_unlock(ser);
1061 return -1;
1062 }
1063
1064 return 0;
1065}
1066
1068{
1071
1073 return -1;
1074 }
1075
1076 ao2_lock(ser);
1077
1078 if (ser->suspended != SERIALIZER_SUSPENDED) {
1079 ao2_unlock(ser);
1080 return 0;
1081 }
1082
1084
1085 /* Notify any other interested threads that this one has awoken */
1086 ast_cond_broadcast(&ser->cond);
1087
1088 /* And now we kick off handling of the queued tasks once again */
1091 }
1092
1093 ao2_unlock(ser);
1094
1095 return 0;
1096}
1097
1098/*!
1099 * \internal
1100 * \brief Clean up resources on Asterisk shutdown
1101 */
1102static void taskpool_shutdown(void)
1103{
1104 if (sched) {
1106 sched = NULL;
1107 }
1108}
1109
1111{
1113 if (!sched) {
1114 return -1;
1115 }
1116
1118 return -1;
1119 }
1120
1122
1123 return 0;
1124}
Prototypes for public functions only of internal interest,.
static void * listener(void *unused)
Definition asterisk.c:1531
Asterisk main include file. File version handling, generic pbx functions.
int ast_register_cleanup(void(*func)(void))
Register a function to be executed before Asterisk gracefully exits.
Definition clicompat.c:19
#define ast_log
Definition astobj2.c:42
@ AO2_ALLOC_OPT_LOCK_NOLOCK
Definition astobj2.h:367
#define ao2_cleanup(obj)
Definition astobj2.h:1934
#define ao2_unlock(a)
Definition astobj2.h:729
#define ao2_lock(a)
Definition astobj2.h:717
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition astobj2.h:459
#define ao2_alloc_options(data_size, destructor_fn, options)
Definition astobj2.h:404
void * ao2_object_get_lockaddr(void *obj)
Return the mutex lock address of an object.
Definition astobj2.c:476
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition astobj2.h:480
#define ao2_alloc(data_size, destructor_fn)
Definition astobj2.h:409
static const char type[]
static const char name[]
Definition format_mp3.c:68
#define LOG_WARNING
#define ast_cond_destroy(cond)
Definition lock.h:209
#define ast_cond_wait(cond, mutex)
Definition lock.h:212
#define ast_cond_init(cond, attr)
Definition lock.h:208
#define ast_mutex_init(pmutex)
Definition lock.h:193
#define ast_mutex_unlock(a)
Definition lock.h:197
#define ast_cond_broadcast(cond)
Definition lock.h:211
pthread_cond_t ast_cond_t
Definition lock.h:185
#define ast_mutex_destroy(a)
Definition lock.h:195
#define ast_mutex_lock(a)
Definition lock.h:196
#define ast_cond_signal(cond)
Definition lock.h:210
static struct ast_serializer_shutdown_group * shutdown_group
#define NULL
Definition resample.c:96
Scheduler Routines (derived from cheops)
#define AST_SCHED_DEL_UNREF(sched, id, refcall)
schedule task to get deleted and call unref function
Definition sched.h:82
int ast_sched_add(struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data) attribute_warn_unused_result
Adds a scheduled event.
Definition sched.c:567
void ast_sched_context_destroy(struct ast_sched_context *c)
destroys a schedule context
Definition sched.c:271
int ast_sched_start_thread(struct ast_sched_context *con)
Start a thread for processing scheduler entries.
Definition sched.c:197
struct ast_sched_context * ast_sched_context_create(void)
Create a scheduler context.
Definition sched.c:238
void ast_serializer_shutdown_group_dec(struct ast_serializer_shutdown_group *shutdown_group)
Decrement the number of serializer members in the group.
void ast_serializer_shutdown_group_inc(struct ast_serializer_shutdown_group *shutdown_group)
Increment the number of serializer members in the group.
Structure for mutex and tracking information.
Definition lock.h:142
void(* thread_start)(void)
Function to call when a taskprocessor starts.
Definition taskpool.h:138
int idle_timeout
Time limit in seconds for idle dynamic taskprocessors.
Definition taskpool.h:88
int max_size
Maximum number of taskprocessors a pool may have.
Definition taskpool.h:122
void(* thread_end)(void)
Function to call when a taskprocessor ends.
Definition taskpool.h:145
int auto_increment
Number of taskprocessors to increment the pool by.
Definition taskpool.h:92
int growth_threshold
The threshold for when to grow the pool.
Definition taskpool.h:131
int minimum_size
Number of taskprocessors that will always exist.
Definition taskpool.h:99
int initial_size
Number of taskprocessors the pool will start with.
Definition taskpool.h:109
An opaque taskpool structure.
Definition taskpool.c:62
taskpool_selector selector
Definition taskpool.c:74
struct ast_taskpool_options options
Definition taskpool.c:70
int shrink_sched_id
Definition taskpool.c:72
struct taskpool_taskprocessors static_taskprocessors
Definition taskpool.c:64
int shutting_down
Definition taskpool.c:68
struct taskpool_taskprocessors dynamic_taskprocessors
Definition taskpool.c:66
char name[0]
Definition taskpool.c:76
void(* task_pushed)(struct ast_taskprocessor_listener *listener, int was_empty)
Indicates a task was pushed to the processor.
A listener for taskprocessors.
struct ast_taskprocessor * tps
A ast_taskprocessor structure is a singleton by name.
Definition sched.c:76
ast_cond_t cond
Definition taskpool.c:725
enum serializer_suspension suspended
Definition taskpool.c:727
struct ast_taskpool * pool
Definition taskpool.c:721
struct ast_serializer_shutdown_group * shutdown_group
Definition taskpool.c:723
ast_cond_t cond
Definition taskpool.c:599
int(* task)(void *)
Definition taskpool.c:602
ast_mutex_t lock
Definition taskpool.c:598
A taskpool taskprocessor.
Definition taskpool.c:34
struct ast_taskprocessor * taskprocessor
Definition taskpool.c:36
struct timeval last_pushed
Definition taskpool.c:38
A container of taskprocessors.
Definition taskpool.c:44
unsigned int taskprocessor_num
Definition taskpool.c:48
struct taskpool_taskprocessors::@436 taskprocessors
int __ast_taskpool_push_wait(struct ast_taskpool *pool, int(*task)(void *data), void *data, const char *file, int line, const char *function)
Push a task to the taskpool, and wait for completion.
Definition taskpool.c:652
static struct ast_taskprocessor_listener_callbacks serializer_tps_listener_callbacks
Definition taskpool.c:836
static void taskpool_least_full_selector(struct ast_taskpool *pool, struct taskpool_taskprocessors *taskprocessors, struct taskpool_taskprocessor **taskprocessor, unsigned int *growth_threshold_reached)
Least full taskprocessor selector.
Definition taskpool.c:301
static void taskpool_sync_task_cleanup(struct taskpool_sync_task *sync_task)
Definition taskpool.c:623
static long taskpool_taskprocessor_load(struct taskpool_taskprocessor *tp)
Definition taskpool.c:90
static struct ast_sched_context * sched
Scheduler used for dynamic pool shrinking.
Definition taskpool.c:97
int ast_taskpool_serializer_unsuspend(struct ast_taskprocessor *serializer)
Unsuspend a serializer, causing tasks to be executed.
Definition taskpool.c:1067
static int taskpool_taskprocessor_start(void *data)
Definition taskpool.c:145
int __ast_taskpool_push(struct ast_taskpool *pool, int(*task)(void *data), void *data, const char *file, int line, const char *function)
Push a task to the taskpool.
Definition taskpool.c:544
static void serializer_dtor(void *obj)
Definition taskpool.c:730
size_t ast_taskpool_taskprocessors_count(struct ast_taskpool *pool)
Get the current number of taskprocessors in the taskpool.
Definition taskpool.c:468
int ast_taskpool_serializer_suspend(struct ast_taskprocessor *serializer)
Suspend a serializer, causing tasks to be queued until unsuspended.
Definition taskpool.c:1018
void(* taskpool_selector)(struct ast_taskpool *pool, struct taskpool_taskprocessors *taskprocessors, struct taskpool_taskprocessor **taskprocessor, unsigned int *growth_threshold_reached)
Definition taskpool.c:51
static void taskpool_shutdown(void)
Definition taskpool.c:1102
static void serializer_task_pushed(struct ast_taskprocessor_listener *listener, int was_empty)
Definition taskpool.c:808
#define TASKPOOL_QUEUE_SIZE_ADD(tps, size)
Definition taskpool.c:479
struct ast_taskprocessor * ast_taskpool_serializer(const char *name, struct ast_taskpool *pool)
Serialized execution of tasks within a ast_taskpool.
Definition taskpool.c:877
void ast_taskpool_shutdown(struct ast_taskpool *pool)
Shut down a taskpool and remove the underlying taskprocessors.
Definition taskpool.c:692
static int taskpool_dynamic_pool_shrink(const void *data)
Definition taskpool.c:240
static int taskpool_taskprocessors_init(struct taskpool_taskprocessors *taskprocessors, unsigned int size)
Definition taskpool.c:207
static void taskpool_taskprocessors_cleanup(struct taskpool_taskprocessors *taskprocessors)
Definition taskpool.c:220
static void taskpool_taskprocessor_dtor(void *obj)
Definition taskpool.c:130
int ast_taskpool_init(void)
Definition taskpool.c:1110
static int taskpool_serializer_suspend_task(void *data)
Definition taskpool.c:995
static struct taskpool_taskprocessor * taskpool_taskprocessor_alloc(struct ast_taskpool *pool, char type)
Definition taskpool.c:166
static void serializer_shutdown(struct ast_taskprocessor_listener *listener)
Definition taskpool.c:826
static struct ast_taskpool * ast_taskpool_get_current(void)
Definition taskpool.c:106
struct ast_taskprocessor * ast_taskpool_serializer_get_current(void)
Get the taskpool serializer currently associated with this thread.
Definition taskpool.c:842
struct ast_taskprocessor * ast_taskpool_serializer_group(const char *name, struct ast_taskpool *pool, struct ast_serializer_shutdown_group *shutdown_group)
Serialized execution of tasks within a ast_taskpool.
Definition taskpool.c:847
static struct serializer * serializer_create(struct ast_taskpool *pool, struct ast_serializer_shutdown_group *shutdown_group)
Definition taskpool.c:739
static int taskpool_taskprocessor_stop(void *data)
Definition taskpool.c:115
static int execute_tasks(void *data)
Definition taskpool.c:759
static void taskpool_sequential_selector(struct ast_taskpool *pool, struct taskpool_taskprocessors *taskprocessors, struct taskpool_taskprocessor **taskprocessor, unsigned int *growth_threshold_reached)
Definition taskpool.c:276
#define TASKPROCESSOR_IS_IDLE(tps, timeout)
Definition taskpool.c:235
#define TASKPOOL_GROW_THRESHOLD
The threshold for a taskprocessor at which we consider the pool needing to grow (50% of high water th...
Definition taskpool.c:80
int __ast_taskpool_serializer_push_wait(struct ast_taskprocessor *serializer, int(*task)(void *data), void *data, const char *file, int line, const char *function)
Push a task to a serializer, and wait for completion.
Definition taskpool.c:899
static int taskpool_sync_task_init(struct taskpool_sync_task *sync_task, int(*task)(void *), void *data)
Definition taskpool.c:609
static int serializer_start(struct ast_taskprocessor_listener *listener)
Definition taskpool.c:820
serializer_suspension
Definition taskpool.c:713
@ SERIALIZER_SUSPENDED
Definition taskpool.c:716
@ SERIALIZER_SUSPENDING
Definition taskpool.c:715
@ SERIALIZER_UNSUSPENDED
Definition taskpool.c:714
static int taskpool_serializer_empty_task(void *data)
Definition taskpool.c:885
struct ast_taskpool * ast_taskpool_create(const char *name, const struct ast_taskpool_options *options)
Create a new taskpool.
Definition taskpool.c:341
static void taskpool_dynamic_pool_grow(struct ast_taskpool *pool, struct taskpool_taskprocessor **taskprocessor)
Definition taskpool.c:496
long ast_taskpool_queue_size(struct ast_taskpool *pool)
Get the current number of queued tasks in the taskpool.
Definition taskpool.c:481
#define AST_TASKPOOL_OPTIONS_VERSION
Definition taskpool.h:76
#define ast_taskpool_push_wait(pool, task, data)
Definition taskpool.h:230
@ AST_TASKPOOL_SELECTOR_SEQUENTIAL
Definition taskpool.h:72
@ AST_TASKPOOL_SELECTOR_LEAST_FULL
Definition taskpool.h:71
@ AST_TASKPOOL_SELECTOR_DEFAULT
Definition taskpool.h:70
#define ast_taskpool_serializer_push_wait(pool, task, data)
Definition taskpool.h:335
#define ast_taskpool_push(pool, task, data)
Definition taskpool.h:210
An API for managing task processing threads that can be shared across modules.
struct ast_taskprocessor * ast_taskprocessor_get(const char *name, enum ast_tps_options create)
Get a reference to a taskprocessor with the specified name and create the taskprocessor if necessary.
struct ast_taskprocessor_listener * ast_taskprocessor_listener_alloc(const struct ast_taskprocessor_listener_callbacks *callbacks, void *user_data)
Allocate a taskprocessor listener.
void * ast_taskprocessor_unreference(struct ast_taskprocessor *tps)
Unreference the specified taskprocessor and its reference count will decrement.
unsigned int ast_taskprocessor_is_executing(const struct ast_taskprocessor *tps)
Return whether the taskprocessor is currently executing a task.
@ TPS_REF_DEFAULT
return a reference to a taskprocessor, create one if it does not exist
void * ast_taskprocessor_listener_get_user_data(const struct ast_taskprocessor_listener *listener)
Get the user data from the listener.
long ast_taskprocessor_size(struct ast_taskprocessor *tps)
Return the current size of the taskprocessor queue.
int ast_taskprocessor_execute(struct ast_taskprocessor *tps)
Pop a task off the taskprocessor and execute it.
#define ast_taskprocessor_push(tps, task_exe, datap)
void ast_taskprocessor_build_name(char *buf, unsigned int size, const char *format,...)
Build a taskprocessor name with a sequence number on the end.
struct ast_taskprocessor * ast_taskprocessor_create_with_listener(const char *name, struct ast_taskprocessor_listener *listener)
Create a taskprocessor with a custom listener.
struct ast_taskprocessor * ast_taskprocessor_listener_get_tps(const struct ast_taskprocessor_listener *listener)
Get a reference to the listener's taskprocessor.
#define AST_TASKPROCESSOR_MAX_NAME
Suggested maximum taskprocessor name length (less null terminator).
int __ast_taskprocessor_push(struct ast_taskprocessor *tps, int(*task_exe)(void *datap), void *datap, const char *file, int line, const char *function) attribute_warn_unused_result
Push a task into the specified taskprocessor queue and signal the taskprocessor thread.
static struct test_options options
static int task(void *data)
Queued task for baseline test.
int ast_threadstorage_set_ptr(struct ast_threadstorage *ts, void *ptr)
Set a raw pointer from threadstorage.
void * ast_threadstorage_get_ptr(struct ast_threadstorage *ts)
Retrieve a raw pointer from threadstorage.
#define AST_THREADSTORAGE_RAW(name)
Time-related functions and macros.
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition time.h:159
Utility functions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition utils.h:981
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition vector.h:637
#define AST_VECTOR_REMOVE_ALL_CMP_UNORDERED(vec, value, cmp, cleanup)
Remove all elements from a vector that matches the given comparison.
Definition vector.h:489
#define AST_VECTOR_FREE(vec)
Deallocates this vector.
Definition vector.h:185
#define AST_VECTOR_INIT(vec, size)
Initialize a vector.
Definition vector.h:124
#define AST_VECTOR_APPEND(vec, elem)
Append an element to a vector, growing the vector if needed.
Definition vector.h:267
#define AST_VECTOR_CALLBACK_VOID(vec, callback,...)
Execute a callback on every element in a vector disregarding callback return.
Definition vector.h:890
#define AST_VECTOR(name, type)
Define a vector structure.
Definition vector.h:44
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition vector.h:708