public inbox for io-uring@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCHSET 0/3] Linked request fix
@ 2026-05-11 18:21 Jens Axboe
  2026-05-11 18:21 ` [PATCH 1/3] io_uring: hold uring_lock when walking link chain in io_wq_free_work() Jens Axboe
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Jens Axboe @ 2026-05-11 18:21 UTC (permalink / raw)
  To: io-uring

Hi,

A small series closing some gaps on linked requests, where iterating
a chain must hold either ->uring_lock OR ->timeout_lock, and modifying
any existing change must hold both. Most cases already do, just a few
gaps that should be buttoned up.

-- 
Jens Axboe


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 1/3] io_uring: hold uring_lock when walking link chain in io_wq_free_work()
  2026-05-11 18:21 [PATCHSET 0/3] Linked request fix Jens Axboe
@ 2026-05-11 18:21 ` Jens Axboe
  2026-05-11 18:21 ` [PATCH 2/3] io_uring: defer linked-timeout chain splice out of hrtimer context Jens Axboe
  2026-05-11 18:21 ` [PATCH 3/3] io_uring: hold uring_lock across io_kill_timeouts() in cancel path Jens Axboe
  2 siblings, 0 replies; 4+ messages in thread
From: Jens Axboe @ 2026-05-11 18:21 UTC (permalink / raw)
  To: io-uring; +Cc: Jens Axboe

io_wq_free_work() calls io_req_find_next() from io-wq worker context,
which reads and clears req->link without holding any lock. This can
potentially race with other paths that mutate the same chain under
ctx->uring_lock.

Take ctx->uring_lock around the io_req_find_next() call. Only requests
with IO_REQ_LINK_FLAGS reach this path, which is not the hot path.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
---
 io_uring/io_uring.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c
index 4ed998d60c09..2ebb0ba37c4f 100644
--- a/io_uring/io_uring.c
+++ b/io_uring/io_uring.c
@@ -1452,8 +1452,13 @@ struct io_wq_work *io_wq_free_work(struct io_wq_work *work)
 	struct io_kiocb *nxt = NULL;
 
 	if (req_ref_put_and_test_atomic(req)) {
-		if (req->flags & IO_REQ_LINK_FLAGS)
+		if (req->flags & IO_REQ_LINK_FLAGS) {
+			struct io_ring_ctx *ctx = req->ctx;
+
+			mutex_lock(&ctx->uring_lock);
 			nxt = io_req_find_next(req);
+			mutex_unlock(&ctx->uring_lock);
+		}
 		io_free_req(req);
 	}
 	return nxt ? &nxt->work : NULL;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/3] io_uring: defer linked-timeout chain splice out of hrtimer context
  2026-05-11 18:21 [PATCHSET 0/3] Linked request fix Jens Axboe
  2026-05-11 18:21 ` [PATCH 1/3] io_uring: hold uring_lock when walking link chain in io_wq_free_work() Jens Axboe
@ 2026-05-11 18:21 ` Jens Axboe
  2026-05-11 18:21 ` [PATCH 3/3] io_uring: hold uring_lock across io_kill_timeouts() in cancel path Jens Axboe
  2 siblings, 0 replies; 4+ messages in thread
From: Jens Axboe @ 2026-05-11 18:21 UTC (permalink / raw)
  To: io-uring; +Cc: Jens Axboe

io_link_timeout_fn() is the hrtimer callback that fires when a linked
timeout expires. It currently calls io_remove_next_linked(prev) under
ctx->timeout_lock to splice the timeout request out of the link chain.
This is the only chain-mutation site that runs without ctx->uring_lock,
because hrtimer callbacks cannot take a mutex. Defer the splicing until
the task_work callback.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
---
 io_uring/timeout.c | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/io_uring/timeout.c b/io_uring/timeout.c
index e2595cae2b07..6353a4d979dc 100644
--- a/io_uring/timeout.c
+++ b/io_uring/timeout.c
@@ -284,6 +284,10 @@ static struct io_kiocb *__io_disarm_linked_timeout(struct io_kiocb *req,
 	struct io_timeout *timeout = io_kiocb_to_cmd(link, struct io_timeout);
 
 	io_remove_next_linked(req);
+
+	/* If this is NULL, then timer already claimed it and will complete it */
+	if (!timeout->head)
+		return NULL;
 	timeout->head = NULL;
 	if (hrtimer_try_to_cancel(&io->timer) != -1) {
 		list_del(&timeout->list);
@@ -367,6 +371,14 @@ static void io_req_task_link_timeout(struct io_tw_req tw_req, io_tw_token_t tw)
 	int ret;
 
 	if (prev) {
+		/*
+		 * splice the linked timeout out of prev's chain if the regular
+		 * completion path didn't already do it.
+		 */
+		if (prev->link == req)
+			prev->link = req->link;
+		req->link = NULL;
+
 		if (!tw.cancel) {
 			struct io_cancel_data cd = {
 				.ctx		= req->ctx,
@@ -401,10 +413,10 @@ static enum hrtimer_restart io_link_timeout_fn(struct hrtimer *timer)
 
 	/*
 	 * We don't expect the list to be empty, that will only happen if we
-	 * race with the completion of the linked work.
+	 * race with the completion of the linked work. Splice of prev is
+	 * done in io_req_task_link_timeout(), if needed.
 	 */
 	if (prev) {
-		io_remove_next_linked(prev);
 		if (!req_ref_inc_not_zero(prev))
 			prev = NULL;
 	}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 3/3] io_uring: hold uring_lock across io_kill_timeouts() in cancel path
  2026-05-11 18:21 [PATCHSET 0/3] Linked request fix Jens Axboe
  2026-05-11 18:21 ` [PATCH 1/3] io_uring: hold uring_lock when walking link chain in io_wq_free_work() Jens Axboe
  2026-05-11 18:21 ` [PATCH 2/3] io_uring: defer linked-timeout chain splice out of hrtimer context Jens Axboe
@ 2026-05-11 18:21 ` Jens Axboe
  2 siblings, 0 replies; 4+ messages in thread
From: Jens Axboe @ 2026-05-11 18:21 UTC (permalink / raw)
  To: io-uring; +Cc: Jens Axboe

io_uring_try_cancel_requests() dropped ctx->uring_lock before calling
io_kill_timeouts(), which walks each timeout's link chain via
io_match_task() to test REQ_F_INFLIGHT. With chain mutation now
serialized by ctx->uring_lock, that walk needs the lock too.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
---
 io_uring/cancel.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/io_uring/cancel.c b/io_uring/cancel.c
index 5e5eb9cfc7cd..4aa3103ba9c3 100644
--- a/io_uring/cancel.c
+++ b/io_uring/cancel.c
@@ -561,8 +561,8 @@ __cold bool io_uring_try_cancel_requests(struct io_ring_ctx *ctx,
 	ret |= io_waitid_remove_all(ctx, tctx, cancel_all);
 	ret |= io_futex_remove_all(ctx, tctx, cancel_all);
 	ret |= io_uring_try_cancel_uring_cmd(ctx, tctx, cancel_all);
-	mutex_unlock(&ctx->uring_lock);
 	ret |= io_kill_timeouts(ctx, tctx, cancel_all);
+	mutex_unlock(&ctx->uring_lock);
 	if (tctx)
 		ret |= io_run_task_work() > 0;
 	else
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-05-11 18:22 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-11 18:21 [PATCHSET 0/3] Linked request fix Jens Axboe
2026-05-11 18:21 ` [PATCH 1/3] io_uring: hold uring_lock when walking link chain in io_wq_free_work() Jens Axboe
2026-05-11 18:21 ` [PATCH 2/3] io_uring: defer linked-timeout chain splice out of hrtimer context Jens Axboe
2026-05-11 18:21 ` [PATCH 3/3] io_uring: hold uring_lock across io_kill_timeouts() in cancel path Jens Axboe

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox