diff --git a/Documentation/reference/os/wqueue.rst b/Documentation/reference/os/wqueue.rst index 020c94cb7a2aa..c5277a3d61f7b 100644 --- a/Documentation/reference/os/wqueue.rst +++ b/Documentation/reference/os/wqueue.rst @@ -149,10 +149,28 @@ and the user-mode work queue is functionally equivalent to the high priority work queue. It differs in that its implementation does not depend on internal, kernel-space facilities. +**Custom User Work Queues**. Applications can use +``work_queue_create()`` to create additional user-mode queues with a +configurable priority and worker pool. The returned handle is passed to +the ``*_wq()`` interfaces and to ``work_queue_free()``. The predefined +``USRWORK`` queue remains available through the queue-ID interfaces. Custom +user-mode queues require pthread support; the predefined ``USRWORK`` queue +does not require pthread support in a protected build. + +**Execution Context**. The user-mode implementation uses mutexes and +semaphores for synchronization. Its queue, cancel, create, priority, and +destroy interfaces must therefore only be called from task context and +must not be called from an interrupt handler. Kernel-mode and flat-build +``work_queue()``, ``work_queue_wq()``, ``work_cancel()``, and +``work_cancel_wq()`` remain safe for interrupt handlers. Creation, +destruction, and synchronous cancellation are task-context operations in +all build modes. + **Configuration Options**. - ``CONFIG_LIBC_USRWORK``. If CONFIG_LIBC_USRWORK is also defined - then the user-mode work queue will be enabled. + then the user-mode work queue will be enabled. Dynamically allocated + user-mode work queues require pthread support. - ``CONFIG_LIBC_USRWORKPRIORITY``. The execution priority of the user-mode priority worker thread. Default: 100 - ``CONFIG_LIBC_USRWORKSTACKSIZE``. The stack size allocated for @@ -202,7 +220,7 @@ Work Queue Interfaces --------------------- .. c:function:: int work_queue(int qid, FAR struct work_s *work, worker_t worker, \ - FAR void *arg, uint32_t delay) + FAR void *arg, clock_t delay) Queue work to be performed at a later time. All queued work will be performed on the worker thread of execution @@ -230,6 +248,56 @@ Work Queue Interfaces :return: Zero is returned on success; a negated errno is returned on failure. +.. c:function:: FAR struct kwork_wqueue_s *work_queue_create( \ + FAR const char *name, int priority, FAR void *stack_addr, \ + int stack_size, int nthreads) + + Create a custom work queue containing ``nthreads`` workers. All + workers use the requested name, priority, and stack size. If + ``stack_addr`` is ``NULL``, each worker stack is allocated by the + thread creation logic. Otherwise, ``stack_addr`` must identify storage + for ``nthreads * stack_size`` bytes. + + This interface must only be called from task context. + + :return: A work queue handle on success; ``NULL`` on failure. + +.. c:function:: int work_queue_free(FAR struct kwork_wqueue_s *wqueue) + + Destroy a custom queue, discard pending work, and wait for all running + callbacks and worker threads to finish. Pending work structures become + available for reuse before the function returns. The predefined + ``HPWORK``, ``LPWORK``, and ``USRWORK`` queues cannot be destroyed. + + This interface must only be called from task context and cannot be + called from one of the queue's own callbacks. + + :return: Zero on success, ``-EINVAL`` for an invalid or predefined + queue, or ``-EDEADLK`` when called by one of the queue's workers. + +.. c:function:: int work_queue_wq(FAR struct kwork_wqueue_s *wqueue, \ + FAR struct work_s *work, worker_t worker, FAR void *arg, \ + clock_t delay) + + Queue work on a custom queue. If the work structure is already pending + on the same queue, the pending instance is replaced. A work structure + must be cancelled before it is moved to another queue. + + :return: Zero on success, ``-EINVAL`` for invalid arguments, or + ``-ESHUTDOWN`` after queue destruction starts. + +.. c:function:: int work_queue_next_wq( \ + FAR struct kwork_wqueue_s *wqueue, \ + FAR struct work_s *work, worker_t worker, FAR void *arg, \ + clock_t delay) + + Queue the next invocation relative to the work structure's previous + expiration time. This avoids accumulating callback execution time in a + periodic schedule. It is normally called from the work callback. + + :return: Zero on success, ``-EINVAL`` for invalid arguments, or + ``-ESHUTDOWN`` after queue destruction starts. + .. c:function:: int work_cancel(int qid, FAR struct work_s *work) Cancel previously queued work. This removes work @@ -240,11 +308,37 @@ Work Queue Interfaces :param work: The previously queued work structure to cancel. :return: Zero is returned on success; a negated ``errno`` is returned on - failure. + failure. Cancelling work that is not queued is a successful no-op. - - ``ENOENT``: There is no such work queued. - ``EINVAL``: An invalid work queue was specified. +.. c:function:: int work_cancel_wq(FAR struct kwork_wqueue_s *wqueue, \ + FAR struct work_s *work) + + Cancel pending work on a custom queue. Cancelling work that is not + queued is a successful no-op. + + :return: Zero on success or ``-EINVAL`` for an invalid argument. + +.. c:function:: int work_cancel_sync_wq( \ + FAR struct kwork_wqueue_s *wqueue, \ + FAR struct work_s *work) + + Cancel pending work and wait for callbacks already using the same work + structure to finish. If called from that work's own callback, the caller + is excluded from the wait to avoid self-deadlock. + + This interface must only be called from task context. + + :return: Zero on success or ``-EINVAL`` for an invalid argument. + +.. c:function:: int work_queue_priority_wq( \ + FAR struct kwork_wqueue_s *wqueue) + + Return the common scheduling priority of a custom queue's worker pool. + + :return: The worker priority on success or a negated errno on failure. + .. c:function:: int work_signal(int qid) Signal the worker thread to process the work @@ -295,4 +389,3 @@ Work Queue Interfaces :param reqprio: Previously requested minimum worker thread priority to be "unboosted". - diff --git a/include/nuttx/wqueue.h b/include/nuttx/wqueue.h index 0bda2d0acbeef..27d0862acc790 100644 --- a/include/nuttx/wqueue.h +++ b/include/nuttx/wqueue.h @@ -74,11 +74,15 @@ * priority worker thread. Default: 2048. * * The user-mode work queue is only available in the protected or kernel - * builds. This those configurations, the user-mode work queue provides the - * same (non-standard) facility for use by applications. + * builds. In those configurations, the user-mode work queue provides the + * same (non-standard) facility for use by applications. User-mode work + * queue APIs use blocking synchronization and must only be called from task + * context. They must not be called from an interrupt handler. * * CONFIG_LIBC_USRWORK. If CONFIG_LIBC_USRWORK is also defined then the - * user-mode work queue will be created. + * user-mode work queue will be created. Dynamically allocated user-mode + * work queues require pthread support. The predefined protected-build + * USRWORK queue does not require pthread support. * CONFIG_LIBC_USRWORKPRIORITY - The minimum execution priority of the lower * priority worker thread. Default: 100 * CONFIG_LIBC_USRWORKSTACKSIZE - The stack size allocated for the lower @@ -332,18 +336,18 @@ int work_usrstart(void); * Name: work_queue_create * * Description: - * Create a new work queue. The work queue is identified by its work - * queue ID, which is used to queue works to the work queue and to - * perform other operations on the work queue. - * This function will create a work thread pool with nthreads threads. - * The work queue ID is returned on success. + * Create a custom work queue and return its handle. The handle is used + * to queue and cancel work, query the worker priority, and destroy the + * queue. This function creates a pool containing nthreads workers. + * This function must only be called from task context. + * User-mode custom queues require pthread support. * * Input Parameters: * name - Name of the new task * priority - Priority of the new task * stack_addr - Stack buffer of the new task * stack_size - size (in bytes) of the stack needed - * nthreads - Number of work thread should be created + * nthreads - Number of worker threads to create * * Returned Value: * The work queue handle returned on success. Otherwise, NULL @@ -359,9 +363,12 @@ FAR struct kwork_wqueue_s *work_queue_create(FAR const char *name, * Name: work_queue_free * * Description: - * Destroy a work queue. The work queue is identified by its work queue ID. - * All worker threads will be destroyed and the work queue will be freed. - * The work queue ID is invalid after this function returns. + * Destroy a custom work queue. All worker threads are stopped and the + * queue is freed. The handle is invalid after this function returns. + * Only a custom queue returned by work_queue_create() may be destroyed; + * the predefined HPWORK, LPWORK, and USRWORK queues cannot be destroyed. + * This function must only be called from task context and must not be + * called by one of the queue's own worker threads. * * Input Parameters: * wqueue - The work queue handle @@ -369,6 +376,9 @@ FAR struct kwork_wqueue_s *work_queue_create(FAR const char *name, * Returned Value: * Zero on success, a negated errno value on failure. * + * -EDEADLK - Called by one of the queue's own worker threads. + * -EINVAL - The handle is NULL or does not identify a custom queue. + * ****************************************************************************/ int work_queue_free(FAR struct kwork_wqueue_s *wqueue); @@ -384,8 +394,14 @@ int work_queue_free(FAR struct kwork_wqueue_s *wqueue); * the caller. Otherwise, the work structure is completely managed by the * work queue logic. The caller should never modify the contents of the * work queue structure directly. If work_queue() is called before the - * previous work has been performed and removed from the queue, then any - * pending work will be canceled and lost. + * previous work has been performed and removed from the same queue, then + * any pending work will be canceled and replaced. A queued work structure + * must be cancelled before it is moved to a different work queue. + * + * work_queue_wq() may be called from interrupt context for a kernel-mode + * or flat-build queue. A user-mode custom queue uses blocking + * synchronization, so work_queue_wq() must only be called from task + * context in user space. * * Input Parameters: * qid - The work queue ID (must be HPWORK or LPWORK) @@ -399,7 +415,10 @@ int work_queue_free(FAR struct kwork_wqueue_s *wqueue); * is invoked. Zero means to perform the work immediately. * * Returned Value: - * Zero on success, a negated errno on failure + * Zero on success, a negated errno on failure. + * + * -EINVAL - An argument or delay is invalid. + * -ESHUTDOWN - The custom work queue is being destroyed. * ****************************************************************************/ @@ -420,6 +439,10 @@ int work_queue_wq(FAR struct kwork_wqueue_s *wqueue, * Note that calling this function outside the work callback requires * the work->qtime being set. * + * A user-mode custom queue uses blocking synchronization, so + * work_queue_next_wq() must only be called from task context in user + * space. + * * Input Parameters: * qid - The work queue ID (must be HPWORK or LPWORK) * wqueue - The work queue handle @@ -432,7 +455,10 @@ int work_queue_wq(FAR struct kwork_wqueue_s *wqueue, * is invoked. Zero means to perform the work immediately. * * Returned Value: - * Zero on success, a negated errno on failure + * Zero on success, a negated errno on failure. + * + * -EINVAL - An argument or delay is invalid. + * -ESHUTDOWN - The custom work queue is being destroyed. * ****************************************************************************/ @@ -466,7 +492,12 @@ int work_queue_priority_wq(FAR struct kwork_wqueue_s *wqueue); * Description: * Cancel previously queued work. This removes work from the work queue. * After work has been cancelled, it may be requeued by calling - * work_queue() again. + * work_queue() again. Cancelling work that is not queued is a successful + * no-op. + * + * work_cancel_wq() may be called from interrupt context for a kernel-mode + * or flat-build queue. It must only be called from task context for a + * user-mode custom queue. * * Input Parameters: * qid - The work queue ID (must be HPWORK or LPWORK) @@ -476,8 +507,7 @@ int work_queue_priority_wq(FAR struct kwork_wqueue_s *wqueue); * Returned Value: * Zero on success, a negated errno on failure * - * -ENOENT - There is no such work queued. - * -EINVAL - An invalid work queue was specified + * -EINVAL - An invalid work queue was specified. * ****************************************************************************/ @@ -489,9 +519,11 @@ int work_cancel_wq(FAR struct kwork_wqueue_s *wqueue, * Name: work_cancel_sync/work_cancel_sync_wq * * Description: - * Blocked cancel previously queued user-mode work. This removes work - * from the user mode work queue. After work has been cancelled, it may - * be requeued by calling work_queue() again. + * Synchronously cancel previously queued work. This removes work from + * the queue and waits for callbacks that are already running. After work + * has been cancelled, it may be requeued by calling work_queue() again. + * Cancelling work that is not queued is a successful no-op. + * This function must only be called from task context. * * Input Parameters: * qid - The work queue ID (must be HPWORK or LPWORK) @@ -499,13 +531,12 @@ int work_cancel_wq(FAR struct kwork_wqueue_s *wqueue, * work - The previously queued work structure to cancel * * Returned Value: - * Zero means the work was successfully cancelled. - * One means the work was not cancelled because it is currently being - * processed by work thread, but wait for it to finish. - * A negated errno value is returned on any failure: + * Zero means that queued work was cancelled and all callbacks using the + * work structure have finished, except for a callback running in the + * caller's own worker thread. A negated errno value is returned on any + * failure: * - * -ENOENT - There is no such work queued. - * -EINVAL - An invalid work queue was specified + * -EINVAL - An invalid work queue was specified. * ****************************************************************************/ diff --git a/libs/libc/wqueue/Kconfig b/libs/libc/wqueue/Kconfig index 0dd9cf425f91f..72f7fb7af8f1a 100644 --- a/libs/libc/wqueue/Kconfig +++ b/libs/libc/wqueue/Kconfig @@ -9,9 +9,12 @@ menu "User Work Queue Support" config LIBC_USRWORK bool "User mode worker thread" default n + depends on BUILD_PROTECTED || !DISABLE_PTHREAD ---help--- - User space work queues can also be made available for deferred - processing in the NuttX kernel build. + User-space work queues provide deferred processing in protected and + kernel builds. Dynamically allocated user-mode work queues require + pthread support. The predefined protected-build USRWORK queue does + not require pthread support. if LIBC_USRWORK diff --git a/libs/libc/wqueue/work_cancel.c b/libs/libc/wqueue/work_cancel.c index 7d4188e974fcb..dabf5f3e80a90 100644 --- a/libs/libc/wqueue/work_cancel.c +++ b/libs/libc/wqueue/work_cancel.c @@ -26,6 +26,7 @@ #include +#include #include #include @@ -57,53 +58,74 @@ * Zero (OK) on success, a negated errno on failure. This error may be * reported: * - * -ENOENT - There is no such work queued. * -EINVAL - An invalid work queue was specified * ****************************************************************************/ -static int work_qcancel(FAR struct usr_wqueue_s *wqueue, +static int work_qcancel(FAR struct usr_wqueue_s *wqueue, bool sync, FAR struct work_s *work) { - int ret = -ENOENT; - int semcount; + pid_t self = gettid(); + int ret; - DEBUGASSERT(work != NULL); + if (wqueue == NULL || work == NULL) + { + return -EINVAL; + } - /* Get exclusive access to the work queue */ + for (; ; ) + { + FAR sem_t *sync_wait = NULL; + int wndx; - while (nxmutex_lock(&wqueue->lock) < 0); + /* Get exclusive access to the work queue */ - /* Cancelling the work is simply a matter of removing the work structure - * from the work queue. This must be done with interrupts disabled because - * new work is typically added to the work queue from interrupt handlers. - */ + do + { + ret = nxmutex_lock(&wqueue->lock); + } + while (ret < 0); - if (work->worker != NULL) - { - bool is_head = list_is_head(&wqueue->q, &work->node); + /* Remove a pending instance from the queue. */ - /* Now, remove the work from the work queue */ + if (work->worker != NULL) + { + if (work_remove(wqueue, work)) + { + work_wake(wqueue); + } + } - list_delete(&work->node); + /* Synchronous cancellation also waits for callbacks that have already + * been dispatched. Never wait for the calling worker itself. Repeat + * because a multi-thread queue can have more than one callback using + * the same work structure. + */ - if (is_head) + if (sync) { - /* Remove the work at the head of the queue */ - - nxsem_get_value(&wqueue->wake, &semcount); - if (semcount < 1) + for (wndx = 0; wndx < wqueue->nthreads; wndx++) { - nxsem_post(&wqueue->wake); + FAR struct usr_worker_s *worker = &wqueue->worker[wndx]; + + if (worker->work == work && self != worker->tid) + { + worker->wait_count++; + sync_wait = &worker->wait; + break; + } } } - work->worker = NULL; - ret = OK; - } + nxmutex_unlock(&wqueue->lock); + + if (sync_wait == NULL) + { + return OK; + } - nxmutex_unlock(&wqueue->lock); - return ret; + nxsem_wait_uninterruptible(sync_wait); + } } /**************************************************************************** @@ -126,15 +148,13 @@ static int work_qcancel(FAR struct usr_wqueue_s *wqueue, * Zero (OK) on success, a negated errno on failure. This error may be * reported: * - * -ENOENT - There is no such work queued. - * ****************************************************************************/ int work_cancel(int qid, FAR struct work_s *work) { if (qid == USRWORK) { - return work_qcancel(&g_usrwork, work); + return work_qcancel(&g_usrwork, false, work); } else { @@ -142,4 +162,30 @@ int work_cancel(int qid, FAR struct work_s *work) } } +#ifndef CONFIG_DISABLE_PTHREAD +int work_cancel_wq(FAR struct kwork_wqueue_s *handle, + FAR struct work_s *work) +{ + return work_qcancel((FAR struct usr_wqueue_s *)handle, false, work); +} +#endif + +int work_cancel_sync(int qid, FAR struct work_s *work) +{ + if (qid == USRWORK) + { + return work_qcancel(&g_usrwork, true, work); + } + + return -EINVAL; +} + +#ifndef CONFIG_DISABLE_PTHREAD +int work_cancel_sync_wq(FAR struct kwork_wqueue_s *handle, + FAR struct work_s *work) +{ + return work_qcancel((FAR struct usr_wqueue_s *)handle, true, work); +} +#endif + #endif /* CONFIG_LIBC_USRWORK && !__KERNEL__ */ diff --git a/libs/libc/wqueue/work_queue.c b/libs/libc/wqueue/work_queue.c index 104e10ff40361..71eeea8f234e4 100644 --- a/libs/libc/wqueue/work_queue.c +++ b/libs/libc/wqueue/work_queue.c @@ -52,10 +52,8 @@ * * The work structure is allocated by caller, but completely managed by * the work queue logic. The caller should never modify the contents of - * the work queue structure; the caller should not call work_qqueue() - * again until either (1) the previous work has been performed and removed - * from the queue, or (2) work_cancel() has been called to cancel the work - * and remove it from the work queue. + * the work queue structure. Calling work_qqueue() while the work is + * pending on the same queue cancels and replaces the pending instance. * * Input Parameters: * wqueue - The work queue @@ -63,7 +61,7 @@ * worker - The worker callback to be invoked. The callback will be * invoked on the worker thread of execution. * arg - The argument that will be passed to the worker callback when - * int is invoked. + * it is invoked. * delay - Delay (in clock ticks) from the time queue until the worker * is invoked. Zero means to perform the work immediately. * @@ -74,21 +72,57 @@ static int work_qqueue(FAR struct usr_wqueue_s *wqueue, FAR struct work_s *work, worker_t worker, - FAR void *arg, clock_t delay) + FAR void *arg, clock_t delay, bool period) { FAR struct work_s *curr; FAR struct work_s *head; - int semcount; + bool wake = false; + int ret; + + if (wqueue == NULL || work == NULL || worker == NULL || + delay < 0 || delay > WDOG_MAX_DELAY) + { + return -EINVAL; + } /* Get exclusive access to the work queue */ - while (nxmutex_lock(&wqueue->lock) < 0); + do + { + ret = nxmutex_lock(&wqueue->lock); + } + while (ret < 0); + + if (wqueue->exit) + { + nxmutex_unlock(&wqueue->lock); + return -ESHUTDOWN; + } + + /* Remove a previous pending instance before requeueing it. */ + + if (work->worker != NULL) + { + wake = work_remove(wqueue, work); + } /* Initialize the work structure */ - work->worker = worker; /* Work callback. non-NULL means queued */ - work->arg = arg; /* Callback argument */ - work->qtime = clock() + delay; /* Delay until work performed */ + work->worker = worker; /* Work callback. non-NULL means queued */ + work->arg = arg; /* Callback argument */ + + if (period) + { + work->qtime += delay; + } + else if (delay > 0) + { + work->qtime = clock() + delay + 1; + } + else + { + work->qtime = clock(); + } /* Insert the work into the wait queue sorted by the expired time. */ @@ -111,17 +145,13 @@ static int work_qqueue(FAR struct usr_wqueue_s *wqueue, list_add_before(&curr->node, &work->node); - /* If the current work is the head of the wait queue. - * We should wake up the worker thread. + /* Wake if this work becomes the new head. Immediate work may be queued + * behind other ready work, so wake another worker in the pool as well. */ - if (curr == head) + if (wake || delay == 0 || curr == head) { - nxsem_get_value(&wqueue->wake, &semcount); - if (semcount < 1) - { - nxsem_post(&wqueue->wake); - } + work_wake(wqueue); } nxmutex_unlock(&wqueue->lock); @@ -166,11 +196,7 @@ int work_queue(int qid, FAR struct work_s *work, worker_t worker, { if (qid == USRWORK) { - /* Is there already pending work? */ - - work_cancel(qid, work); - - return work_qqueue(&g_usrwork, work, worker, arg, delay); + return work_qqueue(&g_usrwork, work, worker, arg, delay, false); } else { @@ -178,4 +204,48 @@ int work_queue(int qid, FAR struct work_s *work, worker_t worker, } } +/**************************************************************************** + * Name: work_queue_wq + * + * Description: + * Queue work on a user-mode custom work queue. This function must only + * be called from task context. + * + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_PTHREAD +int work_queue_wq(FAR struct kwork_wqueue_s *handle, + FAR struct work_s *work, worker_t worker, + FAR void *arg, clock_t delay) +{ + return work_qqueue((FAR struct usr_wqueue_s *)handle, work, + worker, arg, delay, false); +} +#endif + +/**************************************************************************** + * Name: work_queue_next/work_queue_next_wq + ****************************************************************************/ + +int work_queue_next(int qid, FAR struct work_s *work, worker_t worker, + FAR void *arg, clock_t delay) +{ + if (qid == USRWORK) + { + return work_qqueue(&g_usrwork, work, worker, arg, delay, true); + } + + return -EINVAL; +} + +#ifndef CONFIG_DISABLE_PTHREAD +int work_queue_next_wq(FAR struct kwork_wqueue_s *handle, + FAR struct work_s *work, worker_t worker, + FAR void *arg, clock_t delay) +{ + return work_qqueue((FAR struct usr_wqueue_s *)handle, work, + worker, arg, delay, true); +} +#endif + #endif /* CONFIG_LIBC_USRWORK && !__KERNEL__ */ diff --git a/libs/libc/wqueue/work_usrthread.c b/libs/libc/wqueue/work_usrthread.c index f4a3663ab1049..1148c6f0789f7 100644 --- a/libs/libc/wqueue/work_usrthread.c +++ b/libs/libc/wqueue/work_usrthread.c @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include @@ -57,11 +59,24 @@ /* The state of the user mode work queue. */ +static struct usr_worker_s g_usrworker = +{ + 0, + NULL, + &g_usrwork, + SEM_INITIALIZER(0), + 0, +}; + struct usr_wqueue_s g_usrwork = { LIST_INITIAL_VALUE(g_usrwork.q), NXMUTEX_INITIALIZER, SEM_INITIALIZER(0), + &g_usrworker, + 1, + false, + false, }; /**************************************************************************** @@ -78,17 +93,18 @@ struct usr_wqueue_s g_usrwork = * be called from application level logic. * * Input Parameters: - * wqueue - Describes the work queue to be processed + * worker - Describes this worker and its parent queue * * Returned Value: - * None + * true to continue processing; false to exit * ****************************************************************************/ -static void work_process(FAR struct usr_wqueue_s *wqueue) +static bool work_process(FAR struct usr_worker_s *worker) { + FAR struct usr_wqueue_s *wqueue = worker->wqueue; FAR struct work_s *work; - worker_t worker; + worker_t callback; FAR void *arg; clock_t tick; clock_t next; @@ -104,7 +120,13 @@ static void work_process(FAR struct usr_wqueue_s *wqueue) { /* Break out earlier if we were awakened by a signal */ - return; + return true; + } + + if (wqueue->exit) + { + nxmutex_unlock(&wqueue->lock); + return false; } /* And check each entry in the work queue. Since we have locked the @@ -133,16 +155,16 @@ static void work_process(FAR struct usr_wqueue_s *wqueue) list_delete(&work->node); /* Extract the work description from the entry (in case the work - * instance by the reused after it has been de-queued). + * instance may be reused after it has been de-queued). */ - worker = work->worker; + callback = work->worker; /* Check for a race condition where the work may be nullified * before it is removed from the queue. */ - if (worker != NULL) + if (callback != NULL) { /* Extract the work argument before unlocking the work queue */ @@ -151,31 +173,57 @@ static void work_process(FAR struct usr_wqueue_s *wqueue) /* Mark the work as no longer being queued */ work->worker = NULL; + worker->work = work; + + /* Let another worker process the next ready entry. */ + + if (!list_is_empty(&wqueue->q)) + { + FAR struct work_s *next_work = + list_first_entry(&wqueue->q, struct work_s, node); + + if (clock_compare(next_work->qtime, tick)) + { + work_wake(wqueue); + } + } /* Do the work. Unlock the work queue while the work is being * performed... we don't have any idea how long this will take! */ nxmutex_unlock(&wqueue->lock); - worker(arg); + callback(arg); /* Now, unfortunately, since we unlocked the work queue we * don't know the state of the work list and we will have to * start back at the head of the list. */ - ret = nxmutex_lock(&wqueue->lock); - if (ret < 0) + do { - /* Break out earlier if we were awakened by a signal */ + ret = nxmutex_lock(&wqueue->lock); + } + while (ret < 0); - return; + worker->work = NULL; + + while (worker->wait_count > 0) + { + worker->wait_count--; + nxsem_post(&worker->wait); + } + + if (wqueue->exit) + { + nxmutex_unlock(&wqueue->lock); + return false; } } } else { - next = work->qtime - clock(); + next = work->qtime - tick; break; } } @@ -207,49 +255,180 @@ static void work_process(FAR struct usr_wqueue_s *wqueue) nxsem_timedwait(&wqueue->wake, &rqtp); } + + return true; } /**************************************************************************** - * Name: work_usrthread + * Name: work_priority + * + * Description: + * Return the common scheduling priority of a user-mode work queue. + * + ****************************************************************************/ + +static int work_priority(FAR struct usr_wqueue_s *wqueue) +{ + struct sched_param param; + int ret; + + if (wqueue == NULL || wqueue->nthreads < 1) + { + return -EINVAL; + } + + ret = sched_getparam(wqueue->worker[0].tid, ¶m); + return ret == OK ? param.sched_priority : -get_errno(); +} + +/**************************************************************************** + * Name: work_pthread * * Description: * This is the worker thread that performs the actions placed on the user * work queue. * - * This is a user mode work queue. It must be used by applications for - * miscellaneous operations. The user work thread must be started by - * application start-up logic by calling work_usrstart(). + * This is a user-mode work queue. The predefined queue is started by + * application start-up logic through work_usrstart(); custom queues are + * started by work_queue_create(). * * Input Parameters: - * argc, argv (not used) + * arg - Describes this worker and its parent queue * * Returned Value: - * Does not return + * NULL * ****************************************************************************/ +static pthread_addr_t work_pthread(pthread_addr_t arg) +{ + FAR struct usr_worker_s *worker = + (FAR struct usr_worker_s *)arg; + + while (work_process(worker)) + { + } + + return NULL; +} + #ifdef CONFIG_BUILD_PROTECTED -static int work_usrthread(int argc, char *argv[]) -#else -static pthread_addr_t work_usrthread(pthread_addr_t arg) +static int work_usrtask(int argc, char *argv[]) +{ + work_pthread(&g_usrworker); + return OK; +} #endif + +#ifndef CONFIG_DISABLE_PTHREAD +/**************************************************************************** + * Name: work_thread_create + * + * Description: + * Create the worker threads for a dynamically allocated user work queue. + * + ****************************************************************************/ + +static int work_thread_create(FAR const char *name, int priority, + FAR void *stack_addr, int stack_size, + FAR struct usr_wqueue_s *wqueue) { - /* Loop forever */ + pthread_attr_t attr; + struct sched_param param; + int created = 0; + int lockret; + int ret; + int wndx; - for (; ; ) + ret = pthread_attr_init(&attr); + if (ret != 0) { - /* Then process queued work. We need to keep the work queue locked - * while we process items in the work list. - */ + return -ret; + } - work_process(&g_usrwork); + ret = pthread_attr_setstacksize(&attr, stack_size); + if (ret != 0) + { + goto errout_with_attr; } -#ifdef CONFIG_BUILD_PROTECTED - return OK; /* To keep some compilers happy */ -#else - return NULL; /* To keep some compilers happy */ -#endif + ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO); + if (ret != 0) + { + goto errout_with_attr; + } + + ret = pthread_attr_getschedparam(&attr, ¶m); + if (ret != 0) + { + goto errout_with_attr; + } + + param.sched_priority = priority; + ret = pthread_attr_setschedparam(&attr, ¶m); + if (ret != 0) + { + goto errout_with_attr; + } + + ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); + if (ret != 0) + { + goto errout_with_attr; + } + + for (wndx = 0; wndx < wqueue->nthreads; wndx++) + { + FAR struct usr_worker_s *worker = &wqueue->worker[wndx]; + + if (stack_addr != NULL) + { + FAR void *stack = (FAR void *) + ((uintptr_t)stack_addr + wndx * stack_size); + + ret = pthread_attr_setstack(&attr, stack, stack_size); + if (ret != 0) + { + goto errout_with_threads; + } + } + + ret = pthread_create(&worker->tid, &attr, work_pthread, worker); + if (ret != 0) + { + goto errout_with_threads; + } + + created++; + pthread_setname_np(worker->tid, name); + } + + pthread_attr_destroy(&attr); + return OK; + +errout_with_threads: + do + { + lockret = nxmutex_lock(&wqueue->lock); + } + while (lockret < 0); + + wqueue->exit = true; + nxmutex_unlock(&wqueue->lock); + + for (wndx = 0; wndx < created; wndx++) + { + nxsem_post(&wqueue->wake); + } + + for (wndx = 0; wndx < created; wndx++) + { + pthread_join(wqueue->worker[wndx].tid, NULL); + } + +errout_with_attr: + pthread_attr_destroy(&attr); + return -ret; } /**************************************************************************** @@ -257,17 +436,161 @@ static pthread_addr_t work_usrthread(pthread_addr_t arg) ****************************************************************************/ /**************************************************************************** - * Name: work_usrstart + * Name: work_queue_create * * Description: - * Start the user mode work queue. + * Create a user-mode custom work queue. This function must only be + * called from task context. * - * Input Parameters: - * None + ****************************************************************************/ + +FAR struct kwork_wqueue_s *work_queue_create(FAR const char *name, + int priority, + FAR void *stack_addr, + int stack_size, int nthreads) +{ + FAR struct usr_wqueue_s *wqueue; + size_t allocsize; + int ret; + int wndx; + + if (name == NULL || stack_size <= 0 || nthreads < 1 || + (size_t)nthreads > (SIZE_MAX - sizeof(*wqueue)) / + sizeof(struct usr_worker_s)) + { + return NULL; + } + + allocsize = sizeof(*wqueue) + + nthreads * sizeof(struct usr_worker_s); + wqueue = calloc(1, allocsize); + if (wqueue == NULL) + { + return NULL; + } + + list_initialize(&wqueue->q); + nxmutex_init(&wqueue->lock); + nxsem_init(&wqueue->wake, 0, 0); + wqueue->worker = (FAR struct usr_worker_s *)(wqueue + 1); + wqueue->nthreads = nthreads; + wqueue->dynamic = true; + + for (wndx = 0; wndx < nthreads; wndx++) + { + wqueue->worker[wndx].wqueue = wqueue; + nxsem_init(&wqueue->worker[wndx].wait, 0, 0); + } + + ret = work_thread_create(name, priority, stack_addr, stack_size, wqueue); + if (ret < 0) + { + for (wndx = 0; wndx < nthreads; wndx++) + { + nxsem_destroy(&wqueue->worker[wndx].wait); + } + + nxsem_destroy(&wqueue->wake); + nxmutex_destroy(&wqueue->lock); + free(wqueue); + return NULL; + } + + return (FAR struct kwork_wqueue_s *)wqueue; +} + +/**************************************************************************** + * Name: work_queue_free * - * Returned Value: - * The task ID of the worker thread is returned on success. A negated - * errno value is returned on failure. + * Description: + * Destroy a user-mode custom work queue. This function must only be + * called from task context and must not be called by one of the queue's + * own worker threads. + * + ****************************************************************************/ + +int work_queue_free(FAR struct kwork_wqueue_s *handle) +{ + FAR struct usr_wqueue_s *wqueue = (FAR struct usr_wqueue_s *)handle; + FAR struct work_s *work; + FAR struct work_s *next; + pid_t self = gettid(); + int ret; + int wndx; + + if (wqueue == NULL || !wqueue->dynamic) + { + return -EINVAL; + } + + for (wndx = 0; wndx < wqueue->nthreads; wndx++) + { + if (self == wqueue->worker[wndx].tid) + { + return -EDEADLK; + } + } + + do + { + ret = nxmutex_lock(&wqueue->lock); + } + while (ret < 0); + + wqueue->exit = true; + + list_for_every_entry_safe(&wqueue->q, work, next, struct work_s, node) + { + list_delete(&work->node); + work->worker = NULL; + } + + nxmutex_unlock(&wqueue->lock); + + for (wndx = 0; wndx < wqueue->nthreads; wndx++) + { + nxsem_post(&wqueue->wake); + } + + for (wndx = 0; wndx < wqueue->nthreads; wndx++) + { + pthread_join(wqueue->worker[wndx].tid, NULL); + nxsem_destroy(&wqueue->worker[wndx].wait); + } + + nxsem_destroy(&wqueue->wake); + nxmutex_destroy(&wqueue->lock); + free(wqueue); + return OK; +} +#endif + +/**************************************************************************** + * Name: work_queue_priority_wq + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_PTHREAD +int work_queue_priority_wq(FAR struct kwork_wqueue_s *handle) +{ + return work_priority((FAR struct usr_wqueue_s *)handle); +} +#endif + +int work_queue_priority(int qid) +{ + if (qid != USRWORK) + { + return -EINVAL; + } + + return work_priority(&g_usrwork); +} + +/**************************************************************************** + * Name: work_usrstart + * + * Description: + * Start the predefined user work queue. * ****************************************************************************/ @@ -275,55 +598,42 @@ int work_usrstart(void) { int ret; #ifndef CONFIG_BUILD_PROTECTED - pthread_t usrwork; pthread_attr_t attr; struct sched_param param; #endif - /* Initialize the work queue */ - - list_initialize(&g_usrwork.q); - #ifdef CONFIG_BUILD_PROTECTED - - /* Start a user-mode worker thread for use by applications. */ - ret = task_create("uwork", CONFIG_LIBC_USRWORKPRIORITY, CONFIG_LIBC_USRWORKSTACKSIZE, - work_usrthread, NULL); + work_usrtask, NULL); if (ret < 0) { int errcode = get_errno(); + DEBUGASSERT(errcode > 0); return -errcode; } - return ret; + g_usrworker.tid = ret; #else - /* Start a user-mode worker thread for use by applications. */ - pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_attr_setstacksize(&attr, CONFIG_LIBC_USRWORKSTACKSIZE); - pthread_attr_getschedparam(&attr, ¶m); param.sched_priority = CONFIG_LIBC_USRWORKPRIORITY; pthread_attr_setschedparam(&attr, ¶m); - ret = pthread_create(&usrwork, &attr, work_usrthread, NULL); + ret = pthread_create(&g_usrworker.tid, &attr, work_pthread, + &g_usrworker); + pthread_attr_destroy(&attr); if (ret != 0) { return -ret; } - - /* Detach because the return value and completion status will not be - * requested. - */ - - pthread_detach(usrwork); - - return (pid_t)usrwork; #endif + + return g_usrworker.tid; } #endif /* CONFIG_LIBC_USRWORK && !__KERNEL__*/ diff --git a/libs/libc/wqueue/wqueue.h b/libs/libc/wqueue/wqueue.h index eab017c6125b9..7daacfc928686 100644 --- a/libs/libc/wqueue/wqueue.h +++ b/libs/libc/wqueue/wqueue.h @@ -29,7 +29,9 @@ #include -#include +#include +#include +#include #include #include @@ -46,13 +48,32 @@ * Public Type Definitions ****************************************************************************/ -/* This structure defines the state of one user-modework queue. */ +/* Forward reference */ + +struct usr_wqueue_s; + +/* This structure describes one user-mode worker thread. */ + +struct usr_worker_s +{ + pid_t tid; /* Worker thread ID */ + FAR struct work_s *work; /* Work currently being processed */ + FAR struct usr_wqueue_s *wqueue; /* Parent work queue */ + sem_t wait; /* Wait for the current work */ + uint16_t wait_count; /* Number of synchronous waiters */ +}; + +/* This structure defines the state of one user-mode work queue. */ struct usr_wqueue_s { - struct list_node q; /* The queue of pending work */ - mutex_t lock; /* exclusive access to user-mode work queue */ - sem_t wake; /* The wake-up semaphore of the usrthread */ + struct list_node q; /* The queue of pending work */ + mutex_t lock; /* Exclusive access to the queue */ + sem_t wake; /* Wake-up semaphore */ + FAR struct usr_worker_s *worker; /* Worker thread state array */ + int nthreads; /* Number of worker threads */ + bool exit; /* Request worker thread exit */ + bool dynamic; /* Dynamically allocated queue */ }; /**************************************************************************** @@ -64,8 +85,49 @@ struct usr_wqueue_s extern struct usr_wqueue_s g_usrwork; /**************************************************************************** - * Public Function Prototypes + * Inline Functions ****************************************************************************/ +/**************************************************************************** + * Name: work_remove + * + * Description: + * Remove work from a user-mode work queue. The caller must hold + * wqueue->lock, and work must be queued on wqueue. + * + * Returned Value: + * true if removing work changed the head of the queue; otherwise false. + * + ****************************************************************************/ + +static inline_function bool +work_remove(FAR struct usr_wqueue_s *wqueue, + FAR struct work_s *work) +{ + FAR struct work_s *head; + + head = list_first_entry(&wqueue->q, struct work_s, node); + + work->worker = NULL; + list_delete(&work->node); + + return head == work; +} + +static inline_function void work_wake(FAR struct usr_wqueue_s *wqueue) +{ + int semcount; + + /* Keep enough wake tokens for the worker pool while bounding stale + * tokens when workers are already running. + */ + + nxsem_get_value(&wqueue->wake, &semcount); + if (semcount < wqueue->nthreads) + { + nxsem_post(&wqueue->wake); + } +} + #endif /* CONFIG_LIBC_USRWORK && !__KERNEL__*/ #endif /* __LIBS_LIBC_WQUEUE_WQUEUE_H */ diff --git a/sched/wqueue/kwork_cancel.c b/sched/wqueue/kwork_cancel.c index 8a8bc6036b158..d2a1b73bd8769 100644 --- a/sched/wqueue/kwork_cancel.c +++ b/sched/wqueue/kwork_cancel.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include "wqueue/wqueue.h" @@ -46,63 +47,65 @@ static int work_qcancel(FAR struct kwork_wqueue_s *wqueue, bool sync, FAR struct work_s *work) { irqstate_t flags; - FAR sem_t *sync_wait = NULL; + pid_t self = sync ? nxsched_gettid() : INVALID_PROCESS_ID; if (wqueue == NULL || work == NULL) { return -EINVAL; } - /* Cancelling the work is simply a matter of removing the work structure - * from the work queue. This must be done with interrupts disabled because - * new work is typically added to the work queue from interrupt handlers. - */ - - flags = spin_lock_irqsave(&wqueue->lock); - - if (!work_available(work)) + for (; ; ) { - /* If the head of the pending queue has changed, we should reset - * the wqueue timer. + FAR struct kworker_s *worker = wq_get_worker(wqueue); + FAR sem_t *sync_wait = NULL; + int wndx; + + /* Cancelling the work is simply a matter of removing the work + * structure from the work queue. This must be done with interrupts + * disabled because new work is typically added from interrupt + * handlers. */ - if (work_remove(wqueue, work)) - { - work_timer_reset(wqueue); - } - } + flags = spin_lock_irqsave(&wqueue->lock); - /* Note that cancel_sync can not be called in the interrupt - * context and the idletask context. - */ + if (!work_available(work)) + { + /* If the head of the pending queue has changed, reset the timer. */ - if (sync) - { - int wndx; - pid_t pid = nxsched_gettid(); - FAR struct kworker_s *worker = wq_get_worker(wqueue); + if (work_remove(wqueue, work)) + { + work_timer_reset(wqueue); + } + } - /* Wait until the worker thread finished the work. */ + /* Synchronous cancellation cannot be called from interrupt or idle + * context. Find one worker still using this work structure. Repeat + * after it finishes because a multi-thread queue can have more than + * one callback using the same work structure. + */ - for (wndx = 0; wndx < wqueue->nthreads; wndx++) + if (sync) { - if (worker[wndx].work == work && worker[wndx].pid != pid) + for (wndx = 0; wndx < wqueue->nthreads; wndx++) { - worker[wndx].wait_count++; - sync_wait = &worker[wndx].wait; - break; + if (worker[wndx].work == work && worker[wndx].pid != self) + { + worker[wndx].wait_count++; + sync_wait = &worker[wndx].wait; + break; + } } } - } - spin_unlock_irqrestore(&wqueue->lock, flags); + spin_unlock_irqrestore(&wqueue->lock, flags); + + if (sync_wait == NULL) + { + return OK; + } - if (sync_wait) - { nxsem_wait_uninterruptible(sync_wait); } - - return 0; } /**************************************************************************** @@ -125,7 +128,6 @@ static int work_qcancel(FAR struct kwork_wqueue_s *wqueue, bool sync, * Returned Value: * Zero on success, a negated errno on failure * - * -ENOENT - There is no such work queued. * -EINVAL - An invalid work queue was specified * ****************************************************************************/ @@ -158,7 +160,6 @@ int work_cancel_wq(FAR struct kwork_wqueue_s *wqueue, * Zero means the work was successfully cancelled. * A negated errno value is returned on any failure: * - * -ENOENT - There is no such work queued. * -EINVAL - An invalid work queue was specified * ****************************************************************************/ diff --git a/sched/wqueue/kwork_queue.c b/sched/wqueue/kwork_queue.c index 75b5a7f5fd9fa..4e36159a66bb1 100644 --- a/sched/wqueue/kwork_queue.c +++ b/sched/wqueue/kwork_queue.c @@ -72,21 +72,33 @@ int work_queue_next_wq(FAR struct kwork_wqueue_s *wqueue, FAR void *arg, clock_t delay) { irqstate_t flags; + bool retimer; + int ret = OK; if (wqueue == NULL || work == NULL || worker == NULL || - delay > WDOG_MAX_DELAY) + delay < 0 || delay > WDOG_MAX_DELAY) { return -EINVAL; } + flags = spin_lock_irqsave(&wqueue->lock); + + if (wqueue->exit) + { + ret = -ESHUTDOWN; + goto out; + } + + /* Remove a previous pending instance before requeueing it. */ + + retimer = work_available(work) ? false : work_remove(wqueue, work); + /* Initialize the work structure. */ work->worker = worker; /* Work callback. non-NULL means queued */ work->arg = arg; /* Callback argument */ work->qtime += delay; /* Expected time based on last expiration time */ - flags = spin_lock_irqsave(&wqueue->lock); - if (delay) { /* Insert to the pending list of the wqueue. */ @@ -95,6 +107,7 @@ int work_queue_next_wq(FAR struct kwork_wqueue_s *wqueue, { /* Start the timer if the work is the earliest expired work. */ + retimer = false; wd_start_abstick(&wqueue->timer, work->qtime, work_timer_expired, (wdparm_t)wqueue); } @@ -106,16 +119,22 @@ int work_queue_next_wq(FAR struct kwork_wqueue_s *wqueue, list_add_tail(&wqueue->expired, &work->node); } + if (retimer) + { + work_timer_reset(wqueue); + } + +out: spin_unlock_irqrestore(&wqueue->lock, flags); - if (!delay) + if (ret == OK && !delay) { /* Immediately wake up the worker thread. */ nxsem_post(&wqueue->sem); } - return 0; + return ret; } int work_queue_next(int qid, FAR struct work_s *work, worker_t worker, @@ -163,7 +182,7 @@ int work_queue_wq(FAR struct kwork_wqueue_s *wqueue, bool retimer; if (wqueue == NULL || work == NULL || worker == NULL || - delay > WDOG_MAX_DELAY) + delay < 0 || delay > WDOG_MAX_DELAY) { return -EINVAL; } @@ -176,6 +195,12 @@ int work_queue_wq(FAR struct kwork_wqueue_s *wqueue, flags = spin_lock_irqsave(&wqueue->lock); + if (wqueue->exit) + { + spin_unlock_irqrestore(&wqueue->lock, flags); + return -ESHUTDOWN; + } + /* Ensure the work has been removed. */ retimer = work_available(work) ? false : work_remove(wqueue, work); diff --git a/sched/wqueue/kwork_thread.c b/sched/wqueue/kwork_thread.c index 4cb84ffb4ea8a..9f0785923e325 100644 --- a/sched/wqueue/kwork_thread.c +++ b/sched/wqueue/kwork_thread.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -199,19 +200,20 @@ static int work_thread(int argc, FAR char *argv[]) kworker = (FAR struct kworker_s *) ((uintptr_t)strtoul(argv[2], NULL, 16)); - /* Loop until wqueue->exit != 0. - * Since the only way to set wqueue->exit is to call work_queue_free(), - * there is no need for entering the critical section. - */ - - while (!wqueue->exit) + for (; ; ) { /* And check first entry in the work queue. Since we have disabled * interrupts we know: (1) we will not be suspended unless we do * so ourselves, and (2) there will be no changes to the work queue */ - flags = spin_lock_irqsave_nopreempt(&wqueue->lock); + flags = spin_lock_irqsave_nopreempt(&wqueue->lock); + + if (wqueue->exit) + { + spin_unlock_irqrestore_nopreempt(&wqueue->lock, flags); + break; + } /* If the wqueue timer is expired and non-active, it indicates that * there might be expired work in the pending queue. @@ -266,6 +268,12 @@ static int work_thread(int argc, FAR char *argv[]) kworker->wait_count--; nxsem_post(&kworker->wait); } + + if (wqueue->exit) + { + spin_unlock_irqrestore_nopreempt(&wqueue->lock, flags); + break; + } } spin_unlock_irqrestore_nopreempt(&wqueue->lock, flags); @@ -306,6 +314,9 @@ static int work_thread_create(FAR const char *name, int priority, FAR char *argv[3]; char arg0[32]; char arg1[32]; + irqstate_t flags; + int created = 0; + int initialized = 0; int wndx; int pid; FAR void *stack = NULL; @@ -319,6 +330,7 @@ static int work_thread_create(FAR const char *name, int priority, for (wndx = 0; wndx < wqueue->nthreads; wndx++) { nxsem_init(&worker[wndx].wait, 0, 0); + initialized++; snprintf(arg0, sizeof(arg0), "%p", wqueue); snprintf(arg1, sizeof(arg1), "%p", &worker[wndx]); @@ -336,19 +348,54 @@ static int work_thread_create(FAR const char *name, int priority, pid = kthread_create_with_stack(name, priority, stack, stack_size, work_thread, argv); - DEBUGASSERT(pid > 0); - if (pid < 0) + if (pid <= 0) { + if (pid == 0) + { + pid = -EIO; + } + serr("ERROR: work_thread_create %d failed: %d\n", wndx, pid); - sched_unlock(); - return pid; + goto errout_with_threads; } worker[wndx].pid = pid; + created++; } sched_unlock(); return OK; + +errout_with_threads: + flags = spin_lock_irqsave_nopreempt(&wqueue->lock); + wqueue->exit = true; + spin_unlock_irqrestore_nopreempt(&wqueue->lock, flags); + + sched_unlock(); + + for (wndx = 0; wndx < created; wndx++) + { + nxsem_post(&wqueue->sem); + } + + for (wndx = 0; wndx < created; wndx++) + { + nxsem_wait_uninterruptible(&wqueue->exsem); + } + + for (wndx = 0; wndx < initialized; wndx++) + { + worker[wndx].pid = INVALID_PROCESS_ID; + nxsem_destroy(&worker[wndx].wait); + } + + nxsem_reset(&wqueue->sem, 0); + nxsem_reset(&wqueue->exsem, 0); + + flags = spin_lock_irqsave_nopreempt(&wqueue->lock); + wqueue->exit = false; + spin_unlock_irqrestore_nopreempt(&wqueue->lock, flags); + return pid; } /**************************************************************************** @@ -373,6 +420,7 @@ void work_timer_expired(wdparm_t arg) */ FAR struct kwork_wqueue_s *wq = (FAR struct kwork_wqueue_s *)arg; + nxsem_post(&wq->sem); } @@ -380,18 +428,15 @@ void work_timer_expired(wdparm_t arg) * Name: work_queue_create * * Description: - * Create a new work queue. The work queue is identified by its work - * queue ID, which is used to queue works to the work queue and to - * perform other operations on the work queue. - * This function will create a work thread pool with nthreads threads. - * The work queue ID is returned on success. + * Create a custom work queue and return its handle. This function creates + * a pool containing nthreads workers. * * Input Parameters: * name - Name of the new task * priority - Priority of the new task * stack_addr - Stack buffer of the new task * stack_size - size (in bytes) of the stack needed - * nthreads - Number of work thread should be created + * nthreads - Number of worker threads to create * * Returned Value: * The work queue handle returned on success. Otherwise, NULL @@ -406,7 +451,9 @@ FAR struct kwork_wqueue_s *work_queue_create(FAR const char *name, FAR struct kwork_wqueue_s *wqueue; int ret; - if (nthreads < 1) + if (name == NULL || stack_size <= 0 || nthreads < 1 || + (size_t)nthreads > (SIZE_MAX - sizeof(*wqueue)) / + sizeof(struct kworker_s)) { return NULL; } @@ -428,6 +475,7 @@ FAR struct kwork_wqueue_s *work_queue_create(FAR const char *name, nxsem_init(&wqueue->sem, 0, 0); nxsem_init(&wqueue->exsem, 0, 0); wqueue->nthreads = nthreads; + wqueue->dynamic = true; spin_lock_init(&wqueue->lock); /* Create the work queue thread pool */ @@ -435,6 +483,8 @@ FAR struct kwork_wqueue_s *work_queue_create(FAR const char *name, ret = work_thread_create(name, priority, stack_addr, stack_size, wqueue); if (ret < 0) { + nxsem_destroy(&wqueue->sem); + nxsem_destroy(&wqueue->exsem); kmm_free(wqueue); return NULL; } @@ -446,12 +496,11 @@ FAR struct kwork_wqueue_s *work_queue_create(FAR const char *name, * Name: work_queue_free * * Description: - * Destroy a work queue. The work queue is identified by its work queue ID. - * All worker threads will be destroyed and the work queue will be freed. - * The work queue ID is invalid after this function returns. + * Destroy a custom work queue. All worker threads are stopped and the + * queue is freed. The handle is invalid after this function returns. * * Input Parameters: - * qid - The work queue ID + * wqueue - The custom work queue handle * * Returned Value: * Zero on success, a negated errno value on failure. @@ -460,19 +509,57 @@ FAR struct kwork_wqueue_s *work_queue_create(FAR const char *name, int work_queue_free(FAR struct kwork_wqueue_s *wqueue) { + FAR struct work_s *work; + FAR struct work_s *next; + FAR struct kworker_s *worker; + irqstate_t flags; + pid_t self; int wndx; - if (wqueue == NULL) + if (wqueue == NULL || !wqueue->dynamic) { return -EINVAL; } - wd_cancel(&wqueue->timer); + worker = wq_get_worker(wqueue); + self = nxsched_gettid(); - /* Mark the work queue as exiting */ + for (wndx = 0; wndx < wqueue->nthreads; wndx++) + { + if (worker[wndx].pid == self) + { + return -EDEADLK; + } + } + + /* Mark the work queue as exiting and return all queued work structures + * to their owners before the queue storage is released. + */ + + flags = spin_lock_irqsave_nopreempt(&wqueue->lock); wqueue->exit = true; + list_for_every_entry_safe(&wqueue->expired, work, next, + struct work_s, node) + { + list_delete(&work->node); + work->worker = NULL; + } + + list_for_every_entry_safe(&wqueue->pending, work, next, + struct work_s, node) + { + list_delete(&work->node); + work->worker = NULL; + } + + spin_unlock_irqrestore_nopreempt(&wqueue->lock, flags); + + /* Stop delayed dispatch after new submissions have been disabled. */ + + wd_cancel(&wqueue->timer); + /* Queue a exit work for all threads */ for (wndx = 0; wndx < wqueue->nthreads; wndx++) @@ -485,6 +572,11 @@ int work_queue_free(FAR struct kwork_wqueue_s *wqueue) nxsem_wait_uninterruptible(&wqueue->exsem); } + for (wndx = 0; wndx < wqueue->nthreads; wndx++) + { + nxsem_destroy(&worker[wndx].wait); + } + nxsem_destroy(&wqueue->sem); nxsem_destroy(&wqueue->exsem); kmm_free(wqueue); diff --git a/sched/wqueue/wqueue.h b/sched/wqueue/wqueue.h index 3ac740729068a..f63d415882971 100644 --- a/sched/wqueue/wqueue.h +++ b/sched/wqueue/wqueue.h @@ -78,8 +78,9 @@ struct kwork_wqueue_s sem_t sem; /* The counting semaphore of the wqueue */ sem_t exsem; /* Sync waiting for thread exit */ spinlock_t lock; /* Spinlock */ - uint8_t nthreads; /* Number of worker threads */ + int nthreads; /* Number of worker threads */ bool exit; /* A flag to request the thread to exit */ + bool dynamic; /* Dynamically allocated queue */ struct wdog_s timer; /* Timer to pending. */ }; @@ -214,6 +215,7 @@ bool work_insert_pending(FAR struct kwork_wqueue_s *wqueue, * * Description: * Internal public function to remove the work from the workqueue. + * The caller must hold wqueue->lock, and work must be queued on wqueue. * Require wqueue != NULL and work != NULL. * * Input Parameters: