From 5a209a853ec0dac623a2d5dfa81dea546b22820d Mon Sep 17 00:00:00 2001 From: raiden00pl Date: Mon, 7 Sep 2026 09:05:14 +0200 Subject: [PATCH] sched/wqueue: restore -ENOENT from work_cancel() for unqueued work work_cancel() used to return -ENOENT when the work structure was not in the queue, and callers depend on that: aio_cancel() tears down the AIO container (file_put() + aioc_free()) only when work_cancel() reports success, because a work item that is not queued may already be executing on a worker thread (see the comment in fs/aio/aio_cancel.c). Since commit 6f72f5481d ("sched/wqueue: Refactor delayed and periodical workqueue") work_cancel() returns OK unconditionally, and commit d2e01b90553 ("sched/wqueue: harden custom queue lifecycle") kept that behaviour and dropped -ENOENT from the function documentation. Under SMP the LTP aio_cancel tests then free the aio container and its file while the lpwork thread is still executing aio_write_worker() on it, which ends in a page fault in file_write() (f_inode == NULL) and a panic. Return -ENOENT again when the work is not queued, and document it. For the synchronous variant "not queued" alone does not tell whether the callback is running: the worker scan does, so report OK when a running callback was found and waited for, and -ENOENT only when the work was neither queued nor running. Assisted-by: Claude Code Signed-off-by: raiden00pl --- sched/wqueue/kwork_cancel.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sched/wqueue/kwork_cancel.c b/sched/wqueue/kwork_cancel.c index 2329abaf708..99c7774f485 100644 --- a/sched/wqueue/kwork_cancel.c +++ b/sched/wqueue/kwork_cancel.c @@ -48,6 +48,7 @@ static int work_qcancel(FAR struct kwork_wqueue_s *wqueue, bool sync, { irqstate_t flags; pid_t self = sync ? nxsched_gettid() : INVALID_PROCESS_ID; + int ret = -ENOENT; if (wqueue == NULL || work == NULL) { @@ -83,8 +84,18 @@ static int work_qcancel(FAR struct kwork_wqueue_s *wqueue, bool sync, { work_timer_reset(wqueue); } + + ret = OK; } + /* Otherwise the work is not queued: either it was never queued or a + * worker has already dequeued it and may be executing its callback + * right now. Only the scan below can tell. Report -ENOENT if the + * work is neither queued nor running, so that callers (e.g. + * aio_cancel()) do not free resources that the callback is still + * using. + */ + if (sync) { for (wndx = 0; wndx < wqueue->nthreads; wndx++) @@ -93,6 +104,7 @@ static int work_qcancel(FAR struct kwork_wqueue_s *wqueue, bool sync, { worker[wndx].wait_count++; sync_wait = &worker[wndx].wait; + ret = OK; break; } } @@ -102,7 +114,7 @@ static int work_qcancel(FAR struct kwork_wqueue_s *wqueue, bool sync, if (sync_wait == NULL) { - return OK; + return ret; } nxsem_wait_uninterruptible(sync_wait); @@ -130,6 +142,8 @@ static int work_qcancel(FAR struct kwork_wqueue_s *wqueue, bool sync, * Zero on success, a negated errno on failure * * -EINVAL - An invalid work queue was specified + * -ENOENT - The work is not queued (and, for the sync variant, not + * running either) * ****************************************************************************/