Skip to content

Commit 8f9eae2

Browse files
bjarki-andreasenkartben
authored andcommitted
kernel: workq: introduce work timeout:
Introduce work timeout, which is an optional workqueue configuration which enables monitoring for work items which take longer than expected. This could be due to long running or deadlocked handlers. Signed-off-by: Bjarki Arge Andreasen <[email protected]>
1 parent 7505ef3 commit 8f9eae2

File tree

4 files changed

+106
-0
lines changed

4 files changed

+106
-0
lines changed

include/zephyr/kernel.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4219,6 +4219,16 @@ struct k_work_queue_config {
42194219
* essential thread.
42204220
*/
42214221
bool essential;
4222+
4223+
/** Controls whether work queue monitors work timeouts.
4224+
*
4225+
* If non-zero, and CONFIG_WORKQUEUE_WORK_TIMEOUT is enabled,
4226+
* the work queue will monitor the duration of each work item.
4227+
* If the work item handler takes longer than the specified
4228+
* time to execute, the work queue thread will be aborted, and
4229+
* an error will be logged if CONFIG_LOG is enabled.
4230+
*/
4231+
uint32_t work_timeout_ms;
42224232
};
42234233

42244234
/** @brief A structure used to hold work until it can be processed. */
@@ -4246,6 +4256,12 @@ struct k_work_q {
42464256

42474257
/* Flags describing queue state. */
42484258
uint32_t flags;
4259+
4260+
#if defined(CONFIG_WORKQUEUE_WORK_TIMEOUT)
4261+
struct _timeout work_timeout_record;
4262+
struct k_work *work;
4263+
k_timeout_t work_timeout;
4264+
#endif /* defined(CONFIG_WORKQUEUE_WORK_TIMEOUT) */
42494265
};
42504266

42514267
/* Provide the implementation for inline functions declared above */

kernel/Kconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,14 @@ endmenu
574574

575575
rsource "Kconfig.obj_core"
576576

577+
config WORKQUEUE_WORK_TIMEOUT
578+
bool "Support workqueue work timeout monitoring"
579+
help
580+
If enabled, the workqueue will monitor the duration of each work item.
581+
If the work item handler takes longer than the specified time to
582+
execute, the work queue thread will be aborted, and an error will be
583+
logged.
584+
577585
menu "System Work Queue Options"
578586
config SYSTEM_WORKQUEUE_STACK_SIZE
579587
int "System workqueue stack size"
@@ -600,6 +608,14 @@ config SYSTEM_WORKQUEUE_NO_YIELD
600608
cooperative and a sequence of work items is expected to complete
601609
without yielding.
602610

611+
config SYSTEM_WORKQUEUE_WORK_TIMEOUT_MS
612+
int "Select system work queue work timeout in milliseconds"
613+
default 5000 if ASSERT
614+
default 0
615+
help
616+
Set to 0 to disable work timeout for system workqueue. Option
617+
has no effect if WORKQUEUE_WORK_TIMEOUT is not enabled.
618+
603619
endmenu
604620

605621
menu "Barrier Operations"

kernel/system_work_q.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ static int k_sys_work_q_init(void)
2525
.name = "sysworkq",
2626
.no_yield = IS_ENABLED(CONFIG_SYSTEM_WORKQUEUE_NO_YIELD),
2727
.essential = true,
28+
.work_timeout_ms = CONFIG_SYSTEM_WORKQUEUE_WORK_TIMEOUT_MS,
2829
};
2930

3031
k_work_queue_start(&k_sys_work_q,

kernel/work.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
#include <errno.h>
1818
#include <ksched.h>
1919
#include <zephyr/sys/printk.h>
20+
#include <zephyr/logging/log.h>
21+
22+
LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL);
2023

2124
static inline void flag_clear(uint32_t *flagp,
2225
uint32_t bit)
@@ -599,6 +602,52 @@ bool k_work_cancel_sync(struct k_work *work,
599602
return pending;
600603
}
601604

605+
#if defined(CONFIG_WORKQUEUE_WORK_TIMEOUT)
606+
static void work_timeout_handler(struct _timeout *record)
607+
{
608+
struct k_work_q *queue = CONTAINER_OF(record, struct k_work_q, work_timeout_record);
609+
struct k_work *work;
610+
k_work_handler_t handler;
611+
const char *name;
612+
const char *space = " ";
613+
614+
K_SPINLOCK(&lock) {
615+
work = queue->work;
616+
handler = work->handler;
617+
}
618+
619+
name = k_thread_name_get(queue->thread_id);
620+
if (name == NULL) {
621+
name = "";
622+
space = "";
623+
}
624+
625+
LOG_ERR("queue %p%s%s blocked by work %p with handler %p",
626+
queue, space, name, work, handler);
627+
628+
k_thread_abort(queue->thread_id);
629+
}
630+
631+
static void work_timeout_start_locked(struct k_work_q *queue, struct k_work *work)
632+
{
633+
if (K_TIMEOUT_EQ(queue->work_timeout, K_FOREVER)) {
634+
return;
635+
}
636+
637+
queue->work = work;
638+
z_add_timeout(&queue->work_timeout_record, work_timeout_handler, queue->work_timeout);
639+
}
640+
641+
static void work_timeout_stop_locked(struct k_work_q *queue)
642+
{
643+
if (K_TIMEOUT_EQ(queue->work_timeout, K_FOREVER)) {
644+
return;
645+
}
646+
647+
z_abort_timeout(&queue->work_timeout_record);
648+
}
649+
#endif /* defined(CONFIG_WORKQUEUE_WORK_TIMEOUT) */
650+
602651
/* Loop executed by a work queue thread.
603652
*
604653
* @param workq_ptr pointer to the work queue structure
@@ -678,6 +727,10 @@ static void work_queue_main(void *workq_ptr, void *p2, void *p3)
678727
continue;
679728
}
680729

730+
#if defined(CONFIG_WORKQUEUE_WORK_TIMEOUT)
731+
work_timeout_start_locked(queue, work);
732+
#endif /* defined(CONFIG_WORKQUEUE_WORK_TIMEOUT) */
733+
681734
k_spin_unlock(&lock, key);
682735

683736
__ASSERT_NO_MSG(handler != NULL);
@@ -690,6 +743,10 @@ static void work_queue_main(void *workq_ptr, void *p2, void *p3)
690743
*/
691744
key = k_spin_lock(&lock);
692745

746+
#if defined(CONFIG_WORKQUEUE_WORK_TIMEOUT)
747+
work_timeout_stop_locked(queue);
748+
#endif /* defined(CONFIG_WORKQUEUE_WORK_TIMEOUT) */
749+
693750
flag_clear(&work->flags, K_WORK_RUNNING_BIT);
694751
if (flag_test(&work->flags, K_WORK_FLUSHING_BIT)) {
695752
finalize_flush_locked(work);
@@ -736,6 +793,14 @@ void k_work_queue_run(struct k_work_q *queue, const struct k_work_queue_config *
736793
k_thread_name_set(_current, cfg->name);
737794
}
738795

796+
#if defined(CONFIG_WORKQUEUE_WORK_TIMEOUT)
797+
if ((cfg != NULL) && (cfg->work_timeout_ms)) {
798+
queue->work_timeout = K_MSEC(cfg->work_timeout_ms);
799+
} else {
800+
queue->work_timeout = K_FOREVER;
801+
}
802+
#endif /* defined(CONFIG_WORKQUEUE_WORK_TIMEOUT) */
803+
739804
sys_slist_init(&queue->pending);
740805
z_waitq_init(&queue->notifyq);
741806
z_waitq_init(&queue->drainq);
@@ -784,6 +849,14 @@ void k_work_queue_start(struct k_work_q *queue,
784849
queue->thread.base.user_options |= K_ESSENTIAL;
785850
}
786851

852+
#if defined(CONFIG_WORKQUEUE_WORK_TIMEOUT)
853+
if ((cfg != NULL) && (cfg->work_timeout_ms)) {
854+
queue->work_timeout = K_MSEC(cfg->work_timeout_ms);
855+
} else {
856+
queue->work_timeout = K_FOREVER;
857+
}
858+
#endif /* defined(CONFIG_WORKQUEUE_WORK_TIMEOUT) */
859+
787860
k_thread_start(&queue->thread);
788861
queue->thread_id = &queue->thread;
789862

0 commit comments

Comments
 (0)