aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Mason <mason@kernel.org>2026-10-25 20:56:17 +0000
committerJens Axboe <axboe@kernel.dk>2026-10-28 10:50:24 -0600
commitcb0306c52ffae083a86fb283de67875ae7a9500e (patch)
tree7356eb6cf604ceb25efc7e1a4daa1e9942d88e3b
parent360395e8dbf9d23579f74b84a1af11190813b0f8 (diff)
io_uring/cancel: drop sqd->lock while SQPOLL waits
An exiting SQPOLL thread calls io_uring_cancel_generic() with sqd->lock held, and keeps it across cond_resched() and the sleep until nothing is in flight anymore. A zero-copy send notification can't be canceled and stays pending as long as the peer doesn't read, so the lock is held for as long as a user cares to. io_ring_exit_work() needs it to park the thread, and so do IORING_REGISTER_IOWQ_AFF and IOWQ_MAX_WORKERS. With 64 such rings sharing the thread, every iou_exit slot sleeps on the lock and no ring gets freed for anyone. Drop the lock around cond_resched() and the sleep, like the main loop of the thread does. Each walk of ctx_list is still under one hold, and nothing is kept across the unlock. The thread drops its io-wq, frees its tctx and clears sqd->thread under one hold after the loop, so whoever holds the lock and finds sqd->thread set can still use them. Fixes: 63809137ebb5 ("io_uring: flush notifiers after sendzc") Signed-off-by: Chris Mason <mason@kernel.org> [axboe: adapted to the schedule_timeout() based loop] Signed-off-by: Jens Axboe <axboe@kernel.dk>
-rw-r--r--io_uring/cancel.c20
1 files changed, 20 insertions, 0 deletions
diff --git a/io_uring/cancel.c b/io_uring/cancel.c
index 6f7a6cf356c2..79b5c133319b 100644
--- a/io_uring/cancel.c
+++ b/io_uring/cancel.c
@@ -703,7 +703,16 @@ __cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd)
}
if (loop) {
+ /*
+ * A request that runs in io-wq and doesn't end makes
+ * each round come here. Don't hold sqd->lock for that
+ * long either.
+ */
+ if (sqd)
+ mutex_unlock(&sqd->lock);
cond_resched();
+ if (sqd)
+ mutex_lock(&sqd->lock);
continue;
}
@@ -741,7 +750,18 @@ __cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd)
if (state & TASK_INTERRUPTIBLE)
timeout = MAX_SCHEDULE_TIMEOUT;
+ /*
+ * A zero-copy notification can't be canceled, and a
+ * peer that doesn't read keeps it pending. Don't hold
+ * sqd->lock while waiting for it, or whoever wants the
+ * lock sleeps as long. No ring is used across this,
+ * the next round walks ->ctx_list anew.
+ */
+ if (sqd)
+ mutex_unlock(&sqd->lock);
schedule_timeout(timeout);
+ if (sqd)
+ mutex_lock(&sqd->lock);
}
end_wait:
finish_wait(&tctx->wait, &wait);