kernel: Add support for stopping workqueues

This patch adds support for stopping workqueues. This is useful for freeing
resources from workqueues when subsystems/modules is deactivated or
cleaning up the system between tests in ztest to reach a fully normalized
state.

The patch adds a new function k_work_queue_stop() that releases the
workqueues thread and stack when a workqueue is unwanted.
k_work_queue_stop(...) should be viewed as a counterpart to
k_work_queue_start(...).

This would allow to:
k_work_queue_start(...);

k_work_drain(..., true);
k_work_queue_stop(...);

Signed-off-by: Måns Ansgariusson <Mansgariusson@gmail.com>
This commit is contained in:
Måns Ansgariusson 2024-12-06 16:45:19 +01:00 committed by Benjamin Cabé
commit 82a9bc4589
2 changed files with 53 additions and 0 deletions

View file

@ -3606,6 +3606,22 @@ int k_work_queue_drain(struct k_work_q *queue, bool plug);
*/
int k_work_queue_unplug(struct k_work_q *queue);
/** @brief Stop a work queue.
*
* Stops the work queue thread and ensures that no further work will be processed.
* This call is blocking and guarantees that the work queue thread has terminated
* cleanly if successful, no work will be processed past this point.
*
* @param queue Pointer to the queue structure.
* @param timeout Maximum time to wait for the work queue to stop.
*
* @retval 0 if the work queue was stopped
* @retval -EALREADY if the work queue was not started (or already stopped)
* @retval -EBUSY if the work queue is actively processing work items
* @retval -ETIMEDOUT if the work queue did not stop within the stipulated timeout
*/
int k_work_queue_stop(struct k_work_q *queue, k_timeout_t timeout);
/** @brief Initialize a delayable work structure.
*
* This must be invoked before scheduling a delayable work structure for the
@ -3915,6 +3931,8 @@ enum {
K_WORK_QUEUE_DRAIN = BIT(K_WORK_QUEUE_DRAIN_BIT),
K_WORK_QUEUE_PLUGGED_BIT = 3,
K_WORK_QUEUE_PLUGGED = BIT(K_WORK_QUEUE_PLUGGED_BIT),
K_WORK_QUEUE_STOP_BIT = 4,
K_WORK_QUEUE_STOP = BIT(K_WORK_QUEUE_STOP_BIT),
/* Static work queue flags */
K_WORK_QUEUE_NO_YIELD_BIT = 8,

View file

@ -653,6 +653,12 @@ static void work_queue_main(void *workq_ptr, void *p2, void *p3)
* submissions.
*/
(void)z_sched_wake_all(&queue->drainq, 1, NULL);
} else if (flag_test(&queue->flags, K_WORK_QUEUE_STOP_BIT)) {
/* User has requested that the queue stop. Clear the status flags and exit.
*/
flags_set(&queue->flags, 0);
k_spin_unlock(&lock, key);
return;
} else {
/* No work is available and no queue state requires
* special handling.
@ -812,6 +818,35 @@ int k_work_queue_unplug(struct k_work_q *queue)
return ret;
}
int k_work_queue_stop(struct k_work_q *queue, k_timeout_t timeout)
{
__ASSERT_NO_MSG(queue);
k_spinlock_key_t key = k_spin_lock(&lock);
if (!flag_test(&queue->flags, K_WORK_QUEUE_STARTED_BIT)) {
k_spin_unlock(&lock, key);
return -EALREADY;
}
if (!flag_test(&queue->flags, K_WORK_QUEUE_PLUGGED_BIT)) {
k_spin_unlock(&lock, key);
return -EBUSY;
}
flag_set(&queue->flags, K_WORK_QUEUE_STOP_BIT);
notify_queue_locked(queue);
k_spin_unlock(&lock, key);
if (k_thread_join(&queue->thread, timeout)) {
key = k_spin_lock(&lock);
flag_clear(&queue->flags, K_WORK_QUEUE_STOP_BIT);
k_spin_unlock(&lock, key);
return -ETIMEDOUT;
}
return 0;
}
#ifdef CONFIG_SYS_CLOCK_EXISTS
/* Timeout handler for delayable work.