Skip to content

Commit f76fe37

Browse files
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 6061deb commit f76fe37

File tree

4 files changed

+93
-0
lines changed

4 files changed

+93
-0
lines changed

include/zephyr/kernel.h

+17
Original file line numberDiff line numberDiff line change
@@ -4168,6 +4168,14 @@ struct k_work_queue_config {
41684168
* essential thread.
41694169
*/
41704170
bool essential;
4171+
4172+
/** Controls whether work queue monitors work timeouts.
4173+
*
4174+
* If set to a positive value, the work queue will monitor the
4175+
* duration of each work item, and warn the user if the work
4176+
* item handler takes longer than work_timeout to execute.
4177+
*/
4178+
uint32_t work_timeout_ms;
41714179
};
41724180

41734181
/** @brief A structure used to hold work until it can be processed. */
@@ -4190,6 +4198,15 @@ struct k_work_q {
41904198

41914199
/* Flags describing queue state. */
41924200
uint32_t flags;
4201+
4202+
/* Timeout which invokes sentinal */
4203+
struct _timeout workto;
4204+
4205+
/* Work monitored by sentinal */
4206+
struct k_work *work;
4207+
4208+
/* Maximum work duration */
4209+
k_timeout_t work_timeout;
41934210
};
41944211

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

kernel/Kconfig

+10
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,9 @@ endmenu
574574

575575
rsource "Kconfig.obj_core"
576576

577+
config WORKQUEUE_WORK_TIMEOUT
578+
bool "Support workqueue work timeout monitoring"
579+
577580
menu "System Work Queue Options"
578581
config SYSTEM_WORKQUEUE_STACK_SIZE
579582
int "System workqueue stack size"
@@ -600,6 +603,13 @@ config SYSTEM_WORKQUEUE_NO_YIELD
600603
cooperative and a sequence of work items is expected to complete
601604
without yielding.
602605

606+
config SYSTEM_WORKQUEUE_WORK_TIMEOUT_MS
607+
int "Select system work queue work timeout in milliseconds"
608+
default 5000 if ASSERT
609+
default 0
610+
help
611+
Set to 0 to disable work timeout for system workqueue.
612+
603613
endmenu
604614

605615
menu "Barrier Operations"

kernel/system_work_q.c

+1
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

+65
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 workto_handler(struct _timeout *to)
607+
{
608+
struct k_work_q *queue = CONTAINER_OF(to, struct k_work_q, workto);
609+
k_spinlock_key_t key;
610+
const char *name;
611+
struct k_work *work;
612+
k_work_handler_t handler;
613+
614+
key = k_spin_lock(&lock);
615+
616+
name = k_thread_name_get(&queue->thread);
617+
work = queue->work;
618+
handler = work->handler;
619+
620+
if (name != NULL) {
621+
LOG_ERR("queue %s blocked by work %p with handler %p", name, work, handler);
622+
} else {
623+
LOG_ERR("queue %p blocked by work %p with handler %p", queue, work, handler);
624+
}
625+
626+
k_spin_unlock(&lock, key);
627+
628+
k_oops();
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->workto, workto_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->workto);
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);
@@ -761,6 +818,14 @@ void k_work_queue_start(struct k_work_q *queue,
761818
queue->thread.base.user_options |= K_ESSENTIAL;
762819
}
763820

821+
#if defined(CONFIG_WORKQUEUE_WORK_TIMEOUT)
822+
if ((cfg != NULL) && (cfg->work_timeout_ms)) {
823+
queue->work_timeout = K_MSEC(cfg->work_timeout_ms);
824+
} else {
825+
queue->work_timeout = K_FOREVER;
826+
}
827+
#endif /* defined(CONFIG_WORKQUEUE_WORK_TIMEOUT) */
828+
764829
k_thread_start(&queue->thread);
765830

766831
SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work_queue, start, queue);

0 commit comments

Comments
 (0)