Skip to content

Commit 7a199dc

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 7a199dc

File tree

4 files changed

+105
-0
lines changed

4 files changed

+105
-0
lines changed

include/zephyr/kernel.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3663,6 +3663,19 @@ int k_work_queue_unplug(struct k_work_q *queue);
36633663
*/
36643664
int k_work_queue_stop(struct k_work_q *queue, k_timeout_t timeout);
36653665

3666+
/** @brief Check if a work queue is blocked.
3667+
*
3668+
* Checks if a work queue is blocked by a work item. The work queue is considered
3669+
* blocked if a work item takes longer to execute than the work_timeout provided
3670+
* to k_work_queue_start in the k_work_queue_config structure.
3671+
*
3672+
* @param queue Pointer to the queue structure.
3673+
*
3674+
* @retval true if the work queue is blocked
3675+
* @retval false if the work queue is not blocked
3676+
*/
3677+
bool k_work_queue_is_blocked(struct k_work_q *queue);
3678+
36663679
/** @brief Initialize a delayable work structure.
36673680
*
36683681
* This must be invoked before scheduling a delayable work structure for the
@@ -3974,6 +3987,8 @@ enum {
39743987
K_WORK_QUEUE_PLUGGED = BIT(K_WORK_QUEUE_PLUGGED_BIT),
39753988
K_WORK_QUEUE_STOP_BIT = 4,
39763989
K_WORK_QUEUE_STOP = BIT(K_WORK_QUEUE_STOP_BIT),
3990+
K_WORK_QUEUE_BLOCKED_BIT = 5,
3991+
K_WORK_QUEUE_BLOCKED = BIT(K_WORK_QUEUE_BLOCKED_BIT),
39773992

39783993
/* Static work queue flags */
39793994
K_WORK_QUEUE_NO_YIELD_BIT = 8,
@@ -4168,6 +4183,14 @@ struct k_work_queue_config {
41684183
* essential thread.
41694184
*/
41704185
bool essential;
4186+
4187+
/** Controls whether work queue monitors work timeouts.
4188+
*
4189+
* If set to a positive value, the work queue will monitor the
4190+
* duration of each work item, and warn the user if the work
4191+
* item handler takes longer than work_timeout to execute.
4192+
*/
4193+
uint32_t work_timeout_ms;
41714194
};
41724195

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

41914214
/* Flags describing queue state. */
41924215
uint32_t flags;
4216+
4217+
/* Timeout which invokes sentinal */
4218+
struct _timeout workto;
4219+
4220+
/* Work monitored by sentinal */
4221+
struct k_work *work;
4222+
4223+
/* Maximum work duration */
4224+
k_timeout_t work_timeout;
41934225
};
41944226

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

kernel/Kconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,13 @@ config SYSTEM_WORKQUEUE_NO_YIELD
600600
cooperative and a sequence of work items is expected to complete
601601
without yielding.
602602

603+
config SYSTEM_WORKQUEUE_WORK_TIMEOUT_MS
604+
int "Select system work queue work timeout in milliseconds"
605+
default 10000 if DEBUG
606+
default 0
607+
help
608+
Set to 0 to disable work timeout for system workqueue.
609+
603610
endmenu
604611

605612
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: 65 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,53 @@ bool k_work_cancel_sync(struct k_work *work,
599602
return pending;
600603
}
601604

605+
static void workto_handler(struct _timeout *to)
606+
{
607+
struct k_work_q *queue = CONTAINER_OF(to, struct k_work_q, workto);
608+
k_spinlock_key_t key;
609+
const char *name;
610+
struct k_work *work;
611+
k_work_handler_t handler;
612+
613+
key = k_spin_lock(&lock);
614+
615+
flag_set(&queue->flags, K_WORK_QUEUE_BLOCKED_BIT);
616+
617+
name = k_thread_name_get(&queue->thread);
618+
work = queue->work;
619+
handler = work->handler;
620+
621+
if (name != NULL) {
622+
LOG_WRN("queue %s blocked by work %p with handler %p", name, work, handler);
623+
} else {
624+
LOG_WRN("queue %p blocked by work %p with handler %p", queue, work, handler);
625+
}
626+
627+
k_spin_unlock(&lock, key);
628+
}
629+
630+
static void work_timeout_start_locked(struct k_work_q *queue, struct k_work *work)
631+
{
632+
if (K_TIMEOUT_EQ(queue->work_timeout, K_FOREVER)) {
633+
return;
634+
}
635+
636+
queue->work = work;
637+
z_add_timeout(&queue->workto, workto_handler, queue->work_timeout);
638+
}
639+
640+
static void work_timeout_stop_locked(struct k_work_q *queue)
641+
{
642+
if (K_TIMEOUT_EQ(queue->work_timeout, K_FOREVER)) {
643+
return;
644+
}
645+
646+
z_abort_timeout(&queue->workto);
647+
if (flag_test_and_clear(&queue->flags, K_WORK_QUEUE_BLOCKED_BIT)) {
648+
LOG_INF("queue %p unblocked", queue);
649+
}
650+
}
651+
602652
/* Loop executed by a work queue thread.
603653
*
604654
* @param workq_ptr pointer to the work queue structure
@@ -678,6 +728,8 @@ static void work_queue_main(void *workq_ptr, void *p2, void *p3)
678728
continue;
679729
}
680730

731+
work_timeout_start_locked(queue, work);
732+
681733
k_spin_unlock(&lock, key);
682734

683735
__ASSERT_NO_MSG(handler != NULL);
@@ -690,6 +742,8 @@ static void work_queue_main(void *workq_ptr, void *p2, void *p3)
690742
*/
691743
key = k_spin_lock(&lock);
692744

745+
work_timeout_stop_locked(queue);
746+
693747
flag_clear(&work->flags, K_WORK_RUNNING_BIT);
694748
if (flag_test(&work->flags, K_WORK_FLUSHING_BIT)) {
695749
finalize_flush_locked(work);
@@ -761,6 +815,12 @@ void k_work_queue_start(struct k_work_q *queue,
761815
queue->thread.base.user_options |= K_ESSENTIAL;
762816
}
763817

818+
if ((cfg != NULL) && (cfg->work_timeout_ms)) {
819+
queue->work_timeout = K_MSEC(cfg->work_timeout_ms);
820+
} else {
821+
queue->work_timeout = K_FOREVER;
822+
}
823+
764824
k_thread_start(&queue->thread);
765825

766826
SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work_queue, start, queue);
@@ -853,6 +913,11 @@ int k_work_queue_stop(struct k_work_q *queue, k_timeout_t timeout)
853913
return 0;
854914
}
855915

916+
bool k_work_queue_is_blocked(struct k_work_q *queue)
917+
{
918+
return flag_test(&queue->flags, K_WORK_QUEUE_BLOCKED_BIT);
919+
}
920+
856921
#ifdef CONFIG_SYS_CLOCK_EXISTS
857922

858923
/* Timeout handler for delayable work.

0 commit comments

Comments
 (0)