Re: [BUG] RCU hang with io_uring nvme polling

Ben Carey <[email protected]>
Newsgroups org.kernel.vger.io-uring,org.kernel.vger.linux-kernel
Message-ID <CA+KFGSpgN7DChCfMK4itc39MB9ubxacbY3sWTByOkG58umvPkQ@mail.gmail.com>
On Fri, Jun 26, 2026 at 12:33 PM Keith Busch <[email protected]> wrote:
> The test has 1 polling queue with 2 jobs dispatching. One of the job's
> polled the completions for both. The other job is polling for no reason
> at all with nothing outstanding. The only thing that can break us out of
> that loop now is need_resched(), but that appears to never return true.

Inspired by this I tried to find a place where one thread polls on a job that's
already finished. I found that a race to io_check_iopoll causes one thread to
enter the polling loop when another has already finished on it. Putting
io_check_iopoll behind a spinlock seems to fix it, though I imagine a more
elegant fix is out there (reusing a different lock, not using expensive locks,
a smarter place to check for racing, etc.)

The diff is as follows:

diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h
index 214fdbd49..e4f76fa74 100644
--- a/include/linux/io_uring_types.h
+++ b/include/linux/io_uring_types.h
@@ -406,6 +406,7 @@ struct io_ring_ctx {
        } ____cacheline_aligned_in_smp;

        spinlock_t              completion_lock;
+       spinlock_t              cq_poll_lock;

        struct list_head        cq_overflow_list;

diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c
index 4d7bcbb97..b65e2b11a 100644
--- a/io_uring/io_uring.c
+++ b/io_uring/io_uring.c
@@ -272,6 +272,7 @@ static __cold struct io_ring_ctx
*io_ring_ctx_alloc(struct io_uring_params *p)
        init_waitqueue_head(&ctx->cq_wait);
        init_waitqueue_head(&ctx->poll_wq);
        spin_lock_init(&ctx->completion_lock);
+       spin_lock_init(&ctx->cq_poll_lock);
        raw_spin_lock_init(&ctx->timeout_lock);
        INIT_LIST_HEAD(&ctx->iopoll_list);
        INIT_LIST_HEAD(&ctx->defer_list);
@@ -1243,7 +1244,13 @@ static int io_iopoll_check(struct io_ring_ctx
*ctx, unsigned int min_events)
                        if (tail != ctx->cached_cq_tail ||
list_empty(&ctx->iopoll_list))
                                break;
                }
-               ret = io_do_iopoll(ctx, !min_events);
+               if (spin_trylock(&ctx->cq_poll_lock)) {
+                       ret = io_do_iopoll(ctx, !min_events);
+                       spin_unlock(&ctx->cq_poll_lock);
+               } else {
+                       ret = 0;
+               }
+
                if (unlikely(ret < 0))
                        return ret;

Best wishes,
Ben Carey
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.