Re: [BUG] RCU hang with io_uring nvme polling
From: Ben Carey
Date: Mon Jun 29 2026 - 16:47:21 EST
On Fri, Jun 26, 2026 at 12:33 PM Keith Busch <kbusch@xxxxxxxxxx> 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