summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJens Axboe <axboe@kernel.dk>2026-09-14 07:42:42 -0600
committerJens Axboe <axboe@kernel.dk>2026-09-14 07:42:42 -0600
commit4953f86cbbdd202104a696ac837a6692bca5e995 (patch)
tree2ab4f8ddfb884173f733233d7205ca70ebbea312
parent1f93fed16929648f427b881d3682bc87ee733d2e (diff)
parent56582c9ccc36003c41e558796ae2bb8ea1093e10 (diff)
downloadlinux-next-4953f86cbbdd202104a696ac837a6692bca5e995.tar.gz
linux-next-4953f86cbbdd202104a696ac837a6692bca5e995.zip
Merge branch 'io_uring-exit-cancel.7' into for-next
* io_uring-exit-cancel.7: io_uring: wait for in-flight requests on ring release io_uring: drop registered files and buffers at release time io_uring: run cancelations synchronously on ring release io_uring/cancel: cancel and wait for all requests on process exit io_uring/notif: count pending zerocopy notifications per ring io_uring/uring_cmd: only cancel requests of the given task io_uring: put request files before posting the completions io_uring/rw: don't reap io-wq IOPOLL completions while io-wq has a reference io_uring: post io-wq completions from the last request reference io_uring/io-wq: put the request file before posting a completion
-rw-r--r--include/linux/io_uring_types.h2
-rw-r--r--io_uring/cancel.c89
-rw-r--r--io_uring/cancel.h13
-rw-r--r--io_uring/io_uring.c214
-rw-r--r--io_uring/notif.c2
-rw-r--r--io_uring/refs.h27
-rw-r--r--io_uring/rw.c4
-rw-r--r--io_uring/timeout.c18
-rw-r--r--io_uring/timeout.h2
-rw-r--r--io_uring/uring_cmd.c4
-rw-r--r--io_uring/uring_cmd.h2
11 files changed, 319 insertions, 58 deletions
diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h
index 39629ee77b91..90fea94ad202 100644
--- a/include/linux/io_uring_types.h
+++ b/include/linux/io_uring_types.h
@@ -522,6 +522,8 @@ struct io_ring_ctx {
/* protected by ->completion_lock */
unsigned nr_req_allocated;
+ /* pending SEND_ZC notifications, protected by ->uring_lock */
+ unsigned nr_notifs;
#ifdef CONFIG_NET_RX_BUSY_POLL
struct list_head napi_list; /* track busy poll napi_id */
diff --git a/io_uring/cancel.c b/io_uring/cancel.c
index 7d7820eab878..911a47075064 100644
--- a/io_uring/cancel.c
+++ b/io_uring/cancel.c
@@ -514,8 +514,9 @@ static __cold bool io_uring_try_cancel_iowq(struct io_ring_ctx *ctx)
__cold bool io_uring_try_cancel_requests(struct io_ring_ctx *ctx,
struct io_uring_task *tctx,
- bool cancel_all, bool is_sqpoll_thread)
+ unsigned int flags)
{
+ bool cancel_all = flags & IO_CANCEL_ALL;
struct io_task_cancel cancel = { .tctx = tctx, .all = cancel_all, };
enum io_wq_cancel cret;
bool ret = false;
@@ -544,7 +545,7 @@ __cold bool io_uring_try_cancel_requests(struct io_ring_ctx *ctx,
/* SQPOLL thread does its own polling */
if ((!(ctx->flags & IORING_SETUP_SQPOLL) && cancel_all) ||
- is_sqpoll_thread) {
+ (flags & IO_CANCEL_SQPOLL)) {
while (!list_empty(&ctx->iopoll_list)) {
io_iopoll_try_reap_events(ctx);
ret = true;
@@ -560,7 +561,9 @@ __cold bool io_uring_try_cancel_requests(struct io_ring_ctx *ctx,
ret |= io_poll_remove_all(ctx, tctx, cancel_all);
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);
+ ret |= io_uring_try_cancel_uring_cmd(ctx, tctx);
+ if (flags & IO_CANCEL_KEEP_TIMEOUTS)
+ cancel_all = false;
ret |= io_kill_timeouts(ctx, tctx, cancel_all);
mutex_unlock(&ctx->uring_lock);
if (tctx)
@@ -576,6 +579,44 @@ static s64 tctx_inflight(struct io_uring_task *tctx, bool tracked)
}
/*
+ * If true, whole thread group is exiting, at which point no task is left that
+ * can reap completions and care about requests in-flight.
+ */
+static bool io_task_group_exiting(void)
+{
+ return current->signal->flags & SIGNAL_GROUP_EXIT;
+}
+
+/*
+ * Return a count of requests an exiting task should wait for.
+ */
+static s64 tctx_inflight_exit(struct io_uring_task *tctx)
+{
+ struct io_tctx_node *node;
+ unsigned long index;
+ s64 inflight;
+
+ inflight = tctx_inflight(tctx, false);
+ xa_for_each(&tctx->xa, index, node) {
+ /* unlocked read is fine, the caller re-evaluates until done */
+ inflight -= data_race(node->ctx->nr_notifs);
+ /* takes ->uring_lock, we hold nothing on the group exit path */
+ inflight -= io_timeouts_armed(node->ctx, tctx);
+ }
+ return inflight;
+}
+
+static bool io_tctx_cancel_done(struct io_uring_task *tctx, bool cancel_all,
+ bool group_exit)
+{
+ if (cancel_all)
+ return !tctx_inflight(tctx, false);
+ if (tctx_inflight(tctx, true))
+ return false;
+ return !group_exit || tctx_inflight_exit(tctx) <= 0;
+}
+
+/*
* Find any io_uring ctx that this task has registered or done IO on, and cancel
* requests. @sqd should be not-null IFF it's an SQPOLL thread cancellation.
*/
@@ -584,7 +625,9 @@ __cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd)
struct io_uring_task *tctx = current->io_uring;
struct io_ring_ctx *ctx;
struct io_tctx_node *node;
+ unsigned int flags = 0;
unsigned long index;
+ bool group_exit;
s64 inflight;
DEFINE_WAIT(wait);
@@ -595,18 +638,30 @@ __cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd)
if (tctx->io_wq)
io_wq_exit_start(tctx->io_wq);
+ /*
+ * If a whole thread group is exiting, nobody will look at completions.
+ * If a single thread is exiting, cancel only those that belong to that
+ * thread.
+ */
+ group_exit = !cancel_all && io_task_group_exiting();
+ if (cancel_all)
+ flags = IO_CANCEL_ALL;
+ else if (group_exit)
+ flags = IO_CANCEL_ALL | IO_CANCEL_KEEP_TIMEOUTS;
+ if (sqd)
+ flags |= IO_CANCEL_SQPOLL;
+
atomic_inc(&tctx->in_cancel);
do {
bool loop = false;
+ unsigned int state;
io_uring_drop_tctx_refs(current);
- if (!tctx_inflight(tctx, !cancel_all))
+ if (io_tctx_cancel_done(tctx, cancel_all, group_exit))
break;
/* read completions before cancelations */
inflight = tctx_inflight(tctx, false);
- if (!inflight)
- break;
if (!sqd) {
xa_for_each(&tctx->xa, index, node) {
@@ -615,15 +670,13 @@ __cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd)
continue;
loop |= io_uring_try_cancel_requests(node->ctx,
current->io_uring,
- cancel_all,
- false);
+ flags);
}
} else {
list_for_each_entry(ctx, &sqd->ctx_list, sqd_list)
loop |= io_uring_try_cancel_requests(ctx,
current->io_uring,
- cancel_all,
- true);
+ flags);
}
if (loop) {
@@ -631,7 +684,12 @@ __cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd)
continue;
}
- prepare_to_wait(&tctx->wait, &wait, TASK_INTERRUPTIBLE);
+ state = TASK_INTERRUPTIBLE;
+ if (task_sigpending(current))
+ state = TASK_UNINTERRUPTIBLE;
+ if (!cancel_all)
+ state |= TASK_FREEZABLE;
+ prepare_to_wait(&tctx->wait, &wait, state);
io_run_task_work();
io_uring_drop_tctx_refs(current);
xa_for_each(&tctx->xa, index, node) {
@@ -646,8 +704,13 @@ __cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd)
* avoids a race where a completion comes in before we did
* prepare_to_wait().
*/
- if (inflight == tctx_inflight(tctx, !cancel_all))
- schedule();
+ if (inflight == tctx_inflight(tctx, false)) {
+ unsigned long timeout = 1;
+
+ if (state & TASK_INTERRUPTIBLE)
+ timeout = MAX_SCHEDULE_TIMEOUT;
+ schedule_timeout(timeout);
+ }
end_wait:
finish_wait(&tctx->wait, &wait);
} while (1);
diff --git a/io_uring/cancel.h b/io_uring/cancel.h
index 1b201a094303..e49713a1bc66 100644
--- a/io_uring/cancel.h
+++ b/io_uring/cancel.h
@@ -30,9 +30,20 @@ bool io_cancel_remove_all(struct io_ring_ctx *ctx, struct io_uring_task *tctx,
int io_cancel_remove(struct io_ring_ctx *ctx, struct io_cancel_data *cd,
unsigned int issue_flags, struct hlist_head *list,
bool (*cancel)(struct io_kiocb *));
+
+/* io_uring_try_cancel_requests() flags */
+enum {
+ /* match all requests, not just REQ_F_INFLIGHT */
+ IO_CANCEL_ALL = 1,
+ /* ignore timeouts */
+ IO_CANCEL_KEEP_TIMEOUTS = 2,
+ /* called by the SQPOLL thread */
+ IO_CANCEL_SQPOLL = 4,
+};
+
__cold bool io_uring_try_cancel_requests(struct io_ring_ctx *ctx,
struct io_uring_task *tctx,
- bool cancel_all, bool is_sqpoll_thread);
+ unsigned int flags);
__cold void io_uring_cancel_generic(bool cancel_all, struct io_sq_data *sqd);
__cold bool io_cancel_ctx_cb(struct io_wq_work *work, void *data);
diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c
index 61053421d809..a67b2adeda36 100644
--- a/io_uring/io_uring.c
+++ b/io_uring/io_uring.c
@@ -907,6 +907,22 @@ bool io_req_post_cqe32(struct io_kiocb *req, struct io_uring_cqe cqe[2])
return posted;
}
+/* drop the request file before the CQE is posted, not deferred after it */
+static void io_req_put_file(struct io_kiocb *req, bool sync)
+{
+ struct file *file = req->file;
+
+ if (!file || (req->flags & (REQ_F_FIXED_FILE | REQ_F_REISSUE)))
+ return;
+
+ WRITE_ONCE(req->file, NULL);
+ /* releasing a ring may wait on other rings, keep that deferred */
+ if (sync && !io_is_uring_fops(file))
+ __fput_sync(file);
+ else
+ fput(file);
+}
+
static void io_req_complete_post(struct io_kiocb *req, unsigned issue_flags)
{
struct io_ring_ctx *ctx = req->ctx;
@@ -919,6 +935,8 @@ static void io_req_complete_post(struct io_kiocb *req, unsigned issue_flags)
if (WARN_ON_ONCE(!(issue_flags & IO_URING_F_IOWQ)))
return;
+ io_req_put_file(req, true);
+
/*
* Handle special CQ sync cases via task_work. DEFER_TASKRUN requires
* the submitter task context, IOPOLL protects with uring_lock.
@@ -939,9 +957,11 @@ defer_complete:
goto defer_complete;
/*
- * We don't free the request here because we know it's called from
- * io-wq only, which holds a reference, so it cannot be the last put.
+ * Request not freed here because we know it's called from io-wq only,
+ * which holds a reference. Hence it can't be the last put. The CQE
+ * has been posted, last put frees it.
*/
+ req->flags |= REQ_F_CQE_SKIP;
req_ref_put(req);
}
@@ -995,8 +1015,6 @@ __cold void io_free_req(struct io_kiocb *req)
{
/* refs were already put, restore them for io_req_task_complete() */
req->flags &= ~REQ_F_REFCOUNT;
- /* we only want to free it, don't post CQEs */
- req->flags |= REQ_F_CQE_SKIP;
req->io_task_work.func = io_req_task_complete;
io_req_task_work_add(req);
}
@@ -1099,6 +1117,8 @@ static void io_free_batch_list(struct io_ring_ctx *ctx,
}
if (req->flags & REQ_F_REFCOUNT) {
node = req->comp_list.next;
+ /* CQE posted, the last put only frees */
+ req->flags |= REQ_F_CQE_SKIP;
if (!req_ref_put_and_test(req))
continue;
}
@@ -1130,6 +1150,19 @@ void __io_submit_flush_completions(struct io_ring_ctx *ctx)
struct io_submit_state *state = &ctx->submit_state;
struct io_wq_work_node *node;
+ /*
+ * Drop the files before the CQEs are posted, so they're released by
+ * the time the completions are visible. Not for requests that are
+ * requeued or still referenced, those aren't freed below.
+ */
+ __wq_list_for_each(node, &state->compl_reqs) {
+ struct io_kiocb *req = container_of(node, struct io_kiocb,
+ comp_list);
+
+ if (!io_req_shared(req))
+ io_req_put_file(req, true);
+ }
+
__io_cq_lock(ctx);
__wq_list_for_each(node, &state->compl_reqs) {
struct io_kiocb *req = container_of(node, struct io_kiocb,
@@ -1262,7 +1295,10 @@ static int io_iopoll_check(struct io_ring_ctx *ctx, unsigned int min_events)
void io_req_task_complete(struct io_tw_req tw_req, io_tw_token_t tw)
{
- io_req_complete_defer(tw_req.req);
+ struct io_kiocb *req = tw_req.req;
+
+ if (io_req_complete_ready(req))
+ io_req_complete_defer(req);
}
/*
@@ -1451,7 +1487,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) {
+ /*
+ * Only continue the link from here if the completion has been
+ * posted. If it's still pending, io_free_req() completes the
+ * request and the next link follows from there, after the CQE.
+ */
+ if ((req->flags & IO_REQ_LINK_FLAGS) &&
+ (req->flags & REQ_F_CQE_SKIP)) {
struct io_ring_ctx *ctx = req->ctx;
mutex_lock(&ctx->uring_lock);
@@ -1480,6 +1522,7 @@ void io_wq_submit_work(struct io_wq_work *work)
/* either cancelled or io-wq is dying, so don't touch tctx->iowq */
if (atomic_read(&work->flags) & IO_WQ_WORK_CANCEL) {
fail:
+ io_req_put_file(req, false);
io_req_task_queue_fail(req, err);
return;
}
@@ -1555,8 +1598,10 @@ fail:
} while (1);
/* avoid locking problems by failing it from a clean context */
- if (ret)
+ if (ret) {
+ io_req_put_file(req, true);
io_req_task_queue_fail(req, ret);
+ }
}
inline struct file *io_file_get_fixed(struct io_kiocb *req, int fd,
@@ -2157,8 +2202,6 @@ static __cold void io_ring_ctx_free(struct io_ring_ctx *ctx)
io_sq_thread_finish(ctx);
mutex_lock(&ctx->uring_lock);
- io_sqe_buffers_unregister(ctx);
- io_sqe_files_unregister(ctx);
io_unregister_zcrx(ctx);
io_cqring_overflow_kill(ctx);
io_eventfd_unregister(ctx);
@@ -2306,18 +2349,18 @@ static __cold void io_tctx_exit_cb(struct callback_head *cb)
complete(&work->completion);
}
-static __cold void io_ring_exit_work(struct work_struct *work)
+/*
+ * Cancel what can be canceled on a dying ring and reap what has completed.
+ * Only waits on polled I/O, never anything else.
+ */
+static __cold void io_ring_ctx_cancel(struct io_ring_ctx *ctx)
{
- struct io_ring_ctx *ctx = container_of(work, struct io_ring_ctx, exit_work);
- unsigned long timeout = jiffies + IO_URING_EXIT_WAIT_MAX;
- unsigned long interval = HZ / 20;
- struct io_tctx_exit exit;
- struct io_tctx_node *node;
- int ret;
+ struct io_sq_data *sqd = ctx->sq_data;
- mutex_lock(&ctx->uring_lock);
- io_terminate_zcrx(ctx);
- mutex_unlock(&ctx->uring_lock);
+ if (test_bit(IO_CHECK_CQ_OVERFLOW_BIT, &ctx->check_cq)) {
+ scoped_guard(mutex, &ctx->uring_lock)
+ io_cqring_overflow_kill(ctx);
+ }
/*
* If we're doing polled IO and end up having requests being
@@ -2326,32 +2369,106 @@ static __cold void io_ring_exit_work(struct work_struct *work)
* as nobody else will be looking for them.
*/
do {
- if (test_bit(IO_CHECK_CQ_OVERFLOW_BIT, &ctx->check_cq)) {
- mutex_lock(&ctx->uring_lock);
- io_cqring_overflow_kill(ctx);
- mutex_unlock(&ctx->uring_lock);
+ if (ctx->flags & IORING_SETUP_DEFER_TASKRUN)
+ io_cancel_local_task_work(ctx);
+ cond_resched();
+ } while (io_uring_try_cancel_requests(ctx, NULL, IO_CANCEL_ALL));
+
+ if (sqd) {
+ struct task_struct *tsk;
+
+ io_sq_thread_park(sqd);
+ tsk = sqpoll_task_locked(sqd);
+ if (tsk && tsk->io_uring && tsk->io_uring->io_wq)
+ io_wq_cancel_cb(tsk->io_uring->io_wq,
+ io_cancel_ctx_cb, ctx, true);
+ io_sq_thread_unpark(sqd);
+ }
+
+ io_req_caches_free(ctx);
+}
+
+/* Number of requests that should be waited for */
+static __cold unsigned int io_ring_ctx_inflight(struct io_ring_ctx *ctx)
+{
+ guard(mutex)(&ctx->uring_lock);
+ __io_req_caches_free(ctx);
+ return ctx->nr_req_allocated - ctx->nr_notifs;
+}
+
+/*
+ * Run task_work completions for current. Only do so if the io_uring callback
+ * itself can get pruned first, otherwise we risk recursing.
+ */
+static __cold bool io_ring_run_own_completions(struct io_uring_task *tctx)
+{
+ unsigned int count = 0;
+
+ if (!tctx || mpscq_empty(&tctx->task_list))
+ return true;
+ if (!task_work_cancel(current, &tctx->task_work))
+ return false;
+ tctx_task_work_run(tctx, UINT_MAX, &count);
+ return true;
+}
+
+/*
+ * Requests may remain after cancelations have been run, as not all requests
+ * are cancelable. Storage I/O is an example. Wait for those so that once
+ * close(2) returns, files pinned by these requests have been released.
+ */
+static __cold void io_ring_ctx_wait_inflight(struct io_ring_ctx *ctx)
+{
+ struct io_uring_task *tctx = current->io_uring;
+ bool ran_own = true;
+
+ if (current->flags & (PF_KTHREAD | PF_EXITING))
+ return;
+ if (tctx && atomic_read(&tctx->in_cancel))
+ return;
+
+ while (io_ring_ctx_inflight(ctx) && !fatal_signal_pending(current)) {
+ unsigned int state;
+
+ if (test_thread_flag(TIF_NOTIFY_SIGNAL)) {
+ clear_notify_signal();
+ if (task_work_pending(current))
+ set_notify_resume(current);
}
+ state = TASK_INTERRUPTIBLE;
+ if (signal_pending(current))
+ state = TASK_KILLABLE;
+ set_current_state(state | TASK_FREEZABLE);
+ /* don't sleep on work that's already there and that we can run */
+ if (ran_own && ((tctx && !mpscq_empty(&tctx->task_list)) ||
+ io_local_work_pending(ctx)))
+ __set_current_state(TASK_RUNNING);
+ else
+ schedule_timeout(1);
- /* The SQPOLL thread never reaches this path */
- do {
- if (ctx->flags & IORING_SETUP_DEFER_TASKRUN)
- io_cancel_local_task_work(ctx);
- cond_resched();
- } while (io_uring_try_cancel_requests(ctx, NULL, true, false));
-
- if (ctx->sq_data) {
- struct io_sq_data *sqd = ctx->sq_data;
- struct task_struct *tsk;
-
- io_sq_thread_park(sqd);
- tsk = sqpoll_task_locked(sqd);
- if (tsk && tsk->io_uring && tsk->io_uring->io_wq)
- io_wq_cancel_cb(tsk->io_uring->io_wq,
- io_cancel_ctx_cb, ctx, true);
- io_sq_thread_unpark(sqd);
+ /* completions may be queued behind us */
+ if (!io_ring_run_own_completions(tctx)) {
+ if (!ran_own)
+ break;
+ ran_own = false;
+ } else {
+ ran_own = true;
}
+ io_ring_ctx_cancel(ctx);
+ }
+}
- io_req_caches_free(ctx);
+static __cold void io_ring_exit_work(struct work_struct *work)
+{
+ struct io_ring_ctx *ctx = container_of(work, struct io_ring_ctx, exit_work);
+ unsigned long timeout = jiffies + IO_URING_EXIT_WAIT_MAX;
+ unsigned long interval = HZ / 20;
+ struct io_tctx_exit exit;
+ struct io_tctx_node *node;
+ int ret;
+
+ do {
+ io_ring_ctx_cancel(ctx);
if (WARN_ON_ONCE(time_after(jiffies, timeout))) {
/* there is little hope left, don't run it too often */
@@ -2419,8 +2536,23 @@ static __cold void io_ring_ctx_wait_and_kill(struct io_ring_ctx *ctx)
percpu_ref_kill(&ctx->refs);
xa_for_each(&ctx->personalities, index, creds)
io_unregister_personality(ctx, index);
+ io_terminate_zcrx(ctx);
+ /* Drop these now, rather than async, to unpin the files */
+ io_sqe_buffers_unregister(ctx);
+ io_sqe_files_unregister(ctx);
mutex_unlock(&ctx->uring_lock);
+ /*
+ * Do the first round of cancelations upfront rather than leaving it
+ * to to exit_work. Anything cancelable is then already on its way
+ * out, and for requests owned by the task closing the ring, this
+ * ensures any held files are put before close(2) returns.
+ */
+ if (!(current->flags & PF_IO_WORKER)) {
+ io_ring_ctx_cancel(ctx);
+ io_ring_ctx_wait_inflight(ctx);
+ }
+
INIT_WORK(&ctx->exit_work, io_ring_exit_work);
/*
* Use system_dfl_wq to avoid spawning tons of event kworkers
diff --git a/io_uring/notif.c b/io_uring/notif.c
index efce8ae12eaa..6c49aff515df 100644
--- a/io_uring/notif.c
+++ b/io_uring/notif.c
@@ -36,6 +36,7 @@ static void io_notif_tw_complete(struct io_tw_req tw_req, io_tw_token_t tw)
}
nd = nd->next;
+ ctx->nr_notifs--;
io_req_task_complete((struct io_tw_req){notif}, tw);
} while (nd);
}
@@ -119,6 +120,7 @@ struct io_kiocb *io_alloc_notif(struct io_ring_ctx *ctx)
if (unlikely(!io_alloc_req(ctx, &notif)))
return NULL;
+ ctx->nr_notifs++;
notif->ctx = ctx;
notif->opcode = IORING_OP_NOP;
notif->flags = 0;
diff --git a/io_uring/refs.h b/io_uring/refs.h
index 0fe16b67c308..021bf0cd66af 100644
--- a/io_uring/refs.h
+++ b/io_uring/refs.h
@@ -56,6 +56,33 @@ static inline void __io_req_set_refcount(struct io_kiocb *req, int nr)
}
}
+/*
+ * io-wq may still hold a reference if the issue completed async. If so, the
+ * last put completes the request, so that file drop and CQE visibility are
+ * ordered. The last reference stays for the free path to put, a linked
+ * timeout may still look at the request until then.
+ */
+static inline bool io_req_complete_ready(struct io_kiocb *req)
+{
+ if (!(req->flags & REQ_F_REFCOUNT) || (req->flags & REQ_F_REISSUE))
+ return true;
+ if (atomic_read(&req->refs) == 1)
+ return true;
+ if (!req_ref_put_and_test(req))
+ return false;
+ /* the other put raced us, ours was the last after all */
+ atomic_set(&req->refs, 1);
+ return true;
+}
+
+/* io-wq still holds a reference, the request can't be completed yet */
+static inline bool io_req_shared(struct io_kiocb *req)
+{
+ if (!(req->flags & REQ_F_REFCOUNT) || (req->flags & REQ_F_REISSUE))
+ return false;
+ return atomic_read(&req->refs) > 1;
+}
+
static inline void io_req_set_refcount(struct io_kiocb *req)
{
__io_req_set_refcount(req, 1);
diff --git a/io_uring/rw.c b/io_uring/rw.c
index 432820f86251..0c9494fd21be 100644
--- a/io_uring/rw.c
+++ b/io_uring/rw.c
@@ -16,6 +16,7 @@
#include "filetable.h"
#include "io_uring.h"
+#include "refs.h"
#include "opdef.h"
#include "kbuf.h"
#include "alloc_cache.h"
@@ -1380,6 +1381,9 @@ int io_do_iopoll(struct io_ring_ctx *ctx, bool force_nonspin)
/* order with io_complete_rw_iopoll(), e.g. ->result updates */
if (!smp_load_acquire(&req->iopoll_completed))
continue;
+ /* io-wq still has a reference, reap it on the next pass */
+ if (io_req_shared(req))
+ continue;
list_del(&req->iopoll_node);
wq_list_add_tail(&req->comp_list, &ctx->submit_state.compl_reqs);
nr_events++;
diff --git a/io_uring/timeout.c b/io_uring/timeout.c
index c4dd26cf342d..9c239c0a1715 100644
--- a/io_uring/timeout.c
+++ b/io_uring/timeout.c
@@ -729,6 +729,24 @@ static bool io_match_task(struct io_kiocb *head, struct io_uring_task *tctx,
return false;
}
+__cold unsigned int io_timeouts_armed(struct io_ring_ctx *ctx,
+ struct io_uring_task *tctx)
+{
+ struct io_timeout *timeout;
+ unsigned int nr = 0;
+
+ guard(mutex)(&ctx->uring_lock);
+ raw_spin_lock_irq(&ctx->timeout_lock);
+ list_for_each_entry(timeout, &ctx->timeout_list, list) {
+ struct io_kiocb *req = cmd_to_io_kiocb(timeout);
+
+ if (req->tctx == tctx)
+ nr += io_linked_nr(req);
+ }
+ raw_spin_unlock_irq(&ctx->timeout_lock);
+ return nr;
+}
+
/* Returns true if we found and killed one or more timeouts */
__cold bool io_kill_timeouts(struct io_ring_ctx *ctx, struct io_uring_task *tctx,
bool cancel_all)
diff --git a/io_uring/timeout.h b/io_uring/timeout.h
index 1620f94dd45a..b6cd608fb667 100644
--- a/io_uring/timeout.h
+++ b/io_uring/timeout.h
@@ -11,6 +11,8 @@ struct io_timeout_data {
__cold void io_flush_timeouts(struct io_ring_ctx *ctx);
struct io_cancel_data;
int io_timeout_cancel(struct io_ring_ctx *ctx, struct io_cancel_data *cd);
+__cold unsigned int io_timeouts_armed(struct io_ring_ctx *ctx,
+ struct io_uring_task *tctx);
__cold bool io_kill_timeouts(struct io_ring_ctx *ctx, struct io_uring_task *tctx,
bool cancel_all);
void io_queue_linked_timeout(struct io_kiocb *req);
diff --git a/io_uring/uring_cmd.c b/io_uring/uring_cmd.c
index 3d5d8b5f4ebb..73b606899833 100644
--- a/io_uring/uring_cmd.c
+++ b/io_uring/uring_cmd.c
@@ -49,7 +49,7 @@ void io_uring_cmd_cleanup(struct io_kiocb *req)
}
bool io_uring_try_cancel_uring_cmd(struct io_ring_ctx *ctx,
- struct io_uring_task *tctx, bool cancel_all)
+ struct io_uring_task *tctx)
{
struct hlist_node *tmp;
struct io_kiocb *req;
@@ -63,7 +63,7 @@ bool io_uring_try_cancel_uring_cmd(struct io_ring_ctx *ctx,
struct io_uring_cmd);
struct file *file = req->file;
- if (!cancel_all && req->tctx != tctx)
+ if (tctx && req->tctx != tctx)
continue;
if (cmd->flags & IORING_URING_CMD_CANCELABLE) {
diff --git a/io_uring/uring_cmd.h b/io_uring/uring_cmd.h
index 041aef8a8aa3..a5cb3f2ee1c5 100644
--- a/io_uring/uring_cmd.h
+++ b/io_uring/uring_cmd.h
@@ -14,7 +14,7 @@ void io_uring_cmd_sqe_copy(struct io_kiocb *req);
void io_uring_cmd_cleanup(struct io_kiocb *req);
bool io_uring_try_cancel_uring_cmd(struct io_ring_ctx *ctx,
- struct io_uring_task *tctx, bool cancel_all);
+ struct io_uring_task *tctx);
bool io_uring_cmd_post_mshot_cqe32(struct io_uring_cmd *cmd,
unsigned int issue_flags,