* [PATCH 1/9] ublk: keep queue canceling over canceled commands
2026-09-28 16:00 [PATCH 0/9] ublk: fix dispatch to canceled io commands Josef Bacik
@ 2026-09-28 16:00 ` Josef Bacik
2026-09-28 16:46 ` Caleb Sander Mateos
2026-09-28 16:00 ` [PATCH 2/9] ublk: clear ub->canceling with the queue's own flag Josef Bacik
` (8 subsequent siblings)
9 siblings, 1 reply; 17+ messages in thread
From: Josef Bacik @ 2026-09-28 16:00 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Caleb Sander Mateos
Cc: linux-block, linux-kernel, linux-doc, Josef Bacik
ublk_queue_rq() dispatches a request to a NULL io->cmd and the kernel
oopses when a queue got ready over commands which were canceled after
being fetched. ublk_mark_io_ready() clears the queue's ->canceling once
q_depth commands got fetched, and that count does not go down when a
fetched command is canceled:
task A task B
FETCH part of the queue
exits, its commands get
canceled
ublk_uring_cmd_cancel_fn()
queues marked canceling
io->cmd = NULL, command done
FETCH the rest of the queue
ublk_mark_io_ready()
queue is ready
->canceling = false
During recovery the disk is attached at that point. Before the first
start START_DEV adds it afterwards.
The UBLK_IO_FLAG_CANCELED left from an earlier round is dropped after
the command is published, and ublk_cancel_dev() does not hold
ub->mutex, so a cancel coming in between loses its flag again.
A UBLK_F_BATCH_IO queue has no per-io flag for this, but STOP_DEV and
QUIESCE_DEV set its ->force_abort when they cancel its fetch commands,
so key on that for such a queue. ->force_abort is only cleared once the
queue is ready again, by ublk_queue_reset_io_flags() after this check
has read it, so one left set by the quiesce of a server which is gone
would keep the queue of the next server canceling. Clear it in
ublk_ch_release_work_fn() once the old server is gone, under ub->mutex
since ublk_stop_dev_unlocked() sets the flag under that mutex and needs
it set until del_gendisk() returns. From then on until the queue is
ready again, requests for a UBLK_F_USER_RECOVERY device are requeued
through ->canceling instead of failed through ->force_abort, as they
are on a queue without UBLK_F_BATCH_IO.
Fix by keeping ->canceling at the ready transition while a command of
the queue carries UBLK_IO_FLAG_CANCELED, or while ->force_abort is set
on a UBLK_F_BATCH_IO queue, and by dropping the stale flag and
publishing a fetched command in one cancel_lock section.
Fixes: 728cbac5fe21 ("ublk: move device reset into ublk_ch_release()")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
drivers/block/ublk_drv.c | 59 +++++++++++++++++++++++++++++++++++++++---------
1 file changed, 48 insertions(+), 11 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 66eb55e7162e..183de080e008 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -2603,6 +2603,16 @@ static void ublk_ch_release_work_fn(struct work_struct *work)
}
}
unlock:
+ /*
+ * A ->force_abort left set here was set against the server which is
+ * gone, clear it so that ublk_queue_reset_io_flags() does not count
+ * it against the next one, whose requests ->canceling holds back
+ * until its queues are ready. Do it under ub->mutex, under which
+ * ublk_stop_dev_unlocked() sets the flag and relies on it until
+ * del_gendisk() returns.
+ */
+ for (i = 0; i < ub->dev_info.nr_hw_queues; i++)
+ WRITE_ONCE(ublk_get_queue(ub, i)->force_abort, false);
mutex_unlock(&ub->mutex);
ublk_put_disk(disk);
@@ -3015,27 +3025,44 @@ static void ublk_stop_dev(struct ublk_device *ub)
ublk_cancel_dev(ub);
}
-static void ublk_reset_io_flags(struct ublk_queue *ubq, struct ublk_io *io)
+static bool ublk_queue_has_canceled_io(const struct ublk_queue *ubq)
+ __must_hold(&ubq->cancel_lock)
{
- /* UBLK_IO_FLAG_CANCELED can be cleared now */
- spin_lock(&ubq->cancel_lock);
- io->flags &= ~UBLK_IO_FLAG_CANCELED;
- spin_unlock(&ubq->cancel_lock);
+ u16 i;
+
+ /*
+ * The ios of a UBLK_F_BATCH_IO queue are not canceled one by one,
+ * canceling the queue sets ->force_abort instead.
+ * ublk_ch_release_work_fn() clears the flag when a server goes away,
+ * so one seen here was not left behind by an earlier server.
+ */
+ if (ublk_support_batch_io(ubq))
+ return READ_ONCE(ubq->force_abort);
+
+ for (i = 0; i < ubq->q_depth; i++) {
+ if (ubq->ios[i].flags & UBLK_IO_FLAG_CANCELED)
+ return true;
+ }
+ return false;
}
/* reset per-queue io flags */
static void ublk_queue_reset_io_flags(struct ublk_queue *ubq)
{
spin_lock(&ubq->cancel_lock);
- ubq->canceling = false;
+ /*
+ * A command canceled after being fetched is still counted as ready
+ * but can't take requests, so the queue has to stay canceling.
+ */
+ if (!ublk_queue_has_canceled_io(ubq))
+ ubq->canceling = false;
spin_unlock(&ubq->cancel_lock);
ubq->fail_io = false;
ubq->force_abort = false;
}
/* device can only be started after all IOs are ready */
-static void ublk_mark_io_ready(struct ublk_device *ub, u16 q_id,
- struct ublk_io *io)
+static void ublk_mark_io_ready(struct ublk_device *ub, u16 q_id)
__must_hold(&ub->mutex)
{
struct ublk_queue *ubq = ublk_get_queue(ub, q_id);
@@ -3044,7 +3071,6 @@ static void ublk_mark_io_ready(struct ublk_device *ub, u16 q_id,
ub->unprivileged_daemons = true;
ubq->nr_io_ready++;
- ublk_reset_io_flags(ubq, io);
/* Check if this specific queue is now fully ready */
if (ublk_queue_ready(ubq)) {
@@ -3269,6 +3295,8 @@ static int ublk_check_fetch_buf(const struct ublk_device *ub, __u64 buf_addr)
static int __ublk_fetch(struct io_uring_cmd *cmd, struct ublk_device *ub,
struct ublk_io *io, u16 q_id)
{
+ struct ublk_queue *ubq = ublk_get_queue(ub, q_id);
+
/* UBLK_IO_FETCH_REQ is only allowed before dev is setup */
if (ublk_dev_ready(ub))
return -EBUSY;
@@ -3279,7 +3307,16 @@ static int __ublk_fetch(struct io_uring_cmd *cmd, struct ublk_device *ub,
WARN_ON_ONCE(io->flags & UBLK_IO_FLAG_OWNED_BY_SRV);
+ /*
+ * ublk_cancel_dev() runs without ub->mutex, publish the command under
+ * cancel_lock so that a cancel either misses the io or finds it whole,
+ * and drop a stale UBLK_IO_FLAG_CANCELED in the same section first so
+ * that one set from now on stays.
+ */
+ spin_lock(&ubq->cancel_lock);
+ io->flags &= ~UBLK_IO_FLAG_CANCELED;
ublk_fill_io_cmd(io, cmd);
+ spin_unlock(&ubq->cancel_lock);
if (ublk_dev_support_batch_io(ub))
WRITE_ONCE(io->task, NULL);
@@ -3306,7 +3343,7 @@ static int ublk_fetch(struct io_uring_cmd *cmd, struct ublk_device *ub,
ret = __ublk_fetch(cmd, ub, io, q_id);
if (!ret) {
ublk_apply_io_buf(ub, io, cmd, buf_addr, &auto_buf, NULL);
- ublk_mark_io_ready(ub, q_id, io);
+ ublk_mark_io_ready(ub, q_id);
}
mutex_unlock(&ub->mutex);
return ret;
@@ -3723,7 +3760,7 @@ static int ublk_batch_prep_io(struct ublk_queue *ubq,
ublk_io_unlock(io);
if (!ret)
- ublk_mark_io_ready(data->ub, ubq->q_id, io);
+ ublk_mark_io_ready(data->ub, ubq->q_id);
return ret;
}
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 1/9] ublk: keep queue canceling over canceled commands
2026-09-28 16:00 ` [PATCH 1/9] ublk: keep queue canceling over canceled commands Josef Bacik
@ 2026-09-28 16:46 ` Caleb Sander Mateos
2026-09-28 18:34 ` Josef Bacik
0 siblings, 1 reply; 17+ messages in thread
From: Caleb Sander Mateos @ 2026-09-28 16:46 UTC (permalink / raw)
To: Josef Bacik; +Cc: Ming Lei, Jens Axboe, linux-block, linux-kernel, linux-doc
On Mon, Sep 28, 2026 at 9:02 AM Josef Bacik <josef@toxicpanda.com> wrote:
>
> ublk_queue_rq() dispatches a request to a NULL io->cmd and the kernel
> oopses when a queue got ready over commands which were canceled after
> being fetched. ublk_mark_io_ready() clears the queue's ->canceling once
> q_depth commands got fetched, and that count does not go down when a
> fetched command is canceled:
>
> task A task B
> FETCH part of the queue
> exits, its commands get
> canceled
> ublk_uring_cmd_cancel_fn()
> queues marked canceling
> io->cmd = NULL, command done
> FETCH the rest of the queue
> ublk_mark_io_ready()
> queue is ready
> ->canceling = false
>
> During recovery the disk is attached at that point. Before the first
> start START_DEV adds it afterwards.
>
> The UBLK_IO_FLAG_CANCELED left from an earlier round is dropped after
> the command is published, and ublk_cancel_dev() does not hold
> ub->mutex, so a cancel coming in between loses its flag again.
>
> A UBLK_F_BATCH_IO queue has no per-io flag for this, but STOP_DEV and
> QUIESCE_DEV set its ->force_abort when they cancel its fetch commands,
> so key on that for such a queue. ->force_abort is only cleared once the
> queue is ready again, by ublk_queue_reset_io_flags() after this check
> has read it, so one left set by the quiesce of a server which is gone
> would keep the queue of the next server canceling. Clear it in
> ublk_ch_release_work_fn() once the old server is gone, under ub->mutex
> since ublk_stop_dev_unlocked() sets the flag under that mutex and needs
> it set until del_gendisk() returns. From then on until the queue is
> ready again, requests for a UBLK_F_USER_RECOVERY device are requeued
> through ->canceling instead of failed through ->force_abort, as they
> are on a queue without UBLK_F_BATCH_IO.
>
> Fix by keeping ->canceling at the ready transition while a command of
> the queue carries UBLK_IO_FLAG_CANCELED, or while ->force_abort is set
> on a UBLK_F_BATCH_IO queue, and by dropping the stale flag and
> publishing a fetched command in one cancel_lock section.
>
> Fixes: 728cbac5fe21 ("ublk: move device reset into ublk_ch_release()")
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> ---
> drivers/block/ublk_drv.c | 59 +++++++++++++++++++++++++++++++++++++++---------
> 1 file changed, 48 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
> index 66eb55e7162e..183de080e008 100644
> --- a/drivers/block/ublk_drv.c
> +++ b/drivers/block/ublk_drv.c
> @@ -2603,6 +2603,16 @@ static void ublk_ch_release_work_fn(struct work_struct *work)
> }
> }
> unlock:
> + /*
> + * A ->force_abort left set here was set against the server which is
> + * gone, clear it so that ublk_queue_reset_io_flags() does not count
> + * it against the next one, whose requests ->canceling holds back
> + * until its queues are ready. Do it under ub->mutex, under which
> + * ublk_stop_dev_unlocked() sets the flag and relies on it until
> + * del_gendisk() returns.
> + */
> + for (i = 0; i < ub->dev_info.nr_hw_queues; i++)
> + WRITE_ONCE(ublk_get_queue(ub, i)->force_abort, false);
I think this has already been done in
https://lore.kernel.org/linux-block/20260821103047.369522-2-yangxiuwei@kylinos.cn/
> mutex_unlock(&ub->mutex);
> ublk_put_disk(disk);
>
> @@ -3015,27 +3025,44 @@ static void ublk_stop_dev(struct ublk_device *ub)
> ublk_cancel_dev(ub);
> }
>
> -static void ublk_reset_io_flags(struct ublk_queue *ubq, struct ublk_io *io)
> +static bool ublk_queue_has_canceled_io(const struct ublk_queue *ubq)
> + __must_hold(&ubq->cancel_lock)
> {
> - /* UBLK_IO_FLAG_CANCELED can be cleared now */
> - spin_lock(&ubq->cancel_lock);
> - io->flags &= ~UBLK_IO_FLAG_CANCELED;
> - spin_unlock(&ubq->cancel_lock);
> + u16 i;
> +
> + /*
> + * The ios of a UBLK_F_BATCH_IO queue are not canceled one by one,
> + * canceling the queue sets ->force_abort instead.
> + * ublk_ch_release_work_fn() clears the flag when a server goes away,
> + * so one seen here was not left behind by an earlier server.
> + */
> + if (ublk_support_batch_io(ubq))
> + return READ_ONCE(ubq->force_abort);
> +
> + for (i = 0; i < ubq->q_depth; i++) {
> + if (ubq->ios[i].flags & UBLK_IO_FLAG_CANCELED)
> + return true;
> + }
> + return false;
> }
>
> /* reset per-queue io flags */
> static void ublk_queue_reset_io_flags(struct ublk_queue *ubq)
> {
> spin_lock(&ubq->cancel_lock);
> - ubq->canceling = false;
> + /*
> + * A command canceled after being fetched is still counted as ready
> + * but can't take requests, so the queue has to stay canceling.
> + */
> + if (!ublk_queue_has_canceled_io(ubq))
> + ubq->canceling = false;
> spin_unlock(&ubq->cancel_lock);
> ubq->fail_io = false;
> ubq->force_abort = false;
> }
>
> /* device can only be started after all IOs are ready */
> -static void ublk_mark_io_ready(struct ublk_device *ub, u16 q_id,
> - struct ublk_io *io)
> +static void ublk_mark_io_ready(struct ublk_device *ub, u16 q_id)
> __must_hold(&ub->mutex)
> {
> struct ublk_queue *ubq = ublk_get_queue(ub, q_id);
> @@ -3044,7 +3071,6 @@ static void ublk_mark_io_ready(struct ublk_device *ub, u16 q_id,
> ub->unprivileged_daemons = true;
>
> ubq->nr_io_ready++;
> - ublk_reset_io_flags(ubq, io);
>
> /* Check if this specific queue is now fully ready */
> if (ublk_queue_ready(ubq)) {
> @@ -3269,6 +3295,8 @@ static int ublk_check_fetch_buf(const struct ublk_device *ub, __u64 buf_addr)
> static int __ublk_fetch(struct io_uring_cmd *cmd, struct ublk_device *ub,
> struct ublk_io *io, u16 q_id)
> {
> + struct ublk_queue *ubq = ublk_get_queue(ub, q_id);
> +
> /* UBLK_IO_FETCH_REQ is only allowed before dev is setup */
> if (ublk_dev_ready(ub))
> return -EBUSY;
> @@ -3279,7 +3307,16 @@ static int __ublk_fetch(struct io_uring_cmd *cmd, struct ublk_device *ub,
>
> WARN_ON_ONCE(io->flags & UBLK_IO_FLAG_OWNED_BY_SRV);
>
> + /*
> + * ublk_cancel_dev() runs without ub->mutex, publish the command under
> + * cancel_lock so that a cancel either misses the io or finds it whole,
> + * and drop a stale UBLK_IO_FLAG_CANCELED in the same section first so
> + * that one set from now on stays.
> + */
> + spin_lock(&ubq->cancel_lock);
> + io->flags &= ~UBLK_IO_FLAG_CANCELED;
> ublk_fill_io_cmd(io, cmd);
> + spin_unlock(&ubq->cancel_lock);
And this looks like it may also duplicate
https://lore.kernel.org/linux-block/20260508123746.242018-1-tom.leiming@gmail.com/
Best,
Caleb
>
> if (ublk_dev_support_batch_io(ub))
> WRITE_ONCE(io->task, NULL);
> @@ -3306,7 +3343,7 @@ static int ublk_fetch(struct io_uring_cmd *cmd, struct ublk_device *ub,
> ret = __ublk_fetch(cmd, ub, io, q_id);
> if (!ret) {
> ublk_apply_io_buf(ub, io, cmd, buf_addr, &auto_buf, NULL);
> - ublk_mark_io_ready(ub, q_id, io);
> + ublk_mark_io_ready(ub, q_id);
> }
> mutex_unlock(&ub->mutex);
> return ret;
> @@ -3723,7 +3760,7 @@ static int ublk_batch_prep_io(struct ublk_queue *ubq,
> ublk_io_unlock(io);
>
> if (!ret)
> - ublk_mark_io_ready(data->ub, ubq->q_id, io);
> + ublk_mark_io_ready(data->ub, ubq->q_id);
>
> return ret;
> }
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 1/9] ublk: keep queue canceling over canceled commands
2026-09-28 16:46 ` Caleb Sander Mateos
@ 2026-09-28 18:34 ` Josef Bacik
0 siblings, 0 replies; 17+ messages in thread
From: Josef Bacik @ 2026-09-28 18:34 UTC (permalink / raw)
To: Caleb Sander Mateos
Cc: Ming Lei, Jens Axboe, linux-block, linux-kernel, linux-doc
On Mon, Sep 28, 2026 at 09:46:04AM -0700, Caleb Sander Mateos wrote:
> On Mon, Sep 28, 2026 at 9:02 AM Josef Bacik <josef@toxicpanda.com> wrote:
> > + for (i = 0; i < ub->dev_info.nr_hw_queues; i++)
> > + WRITE_ONCE(ublk_get_queue(ub, i)->force_abort, false);
>
> I think this has already been done in
> https://lore.kernel.org/linux-block/20260821103047.369522-2-yangxiuwei@kylinos.cn/
Not quite. Yang's patch went in as 8a14be55bdc6 ("ublk: clear
force_abort in ublk_queue_reset_io_flags()") and this series is on top
of it, so that clear is still there. The problem is that this patch has
the ready transition of a UBLK_F_BATCH_IO queue look at ->force_abort to
decide whether the queue has to stay canceling, and it reads the flag
before 8a14be55bdc6 clears it. A ->force_abort left over from the old
server's quiesce then keeps the new server's queue canceling and the
recovery fails, so it has to be cleared earlier, when the old server
goes away.
You're right that clearing it in two places makes no sense though. For
v2 I'll move the clear instead of adding one: drop it from
ublk_queue_reset_io_flags(), since the release work covers Yang's case
too (START_USER_RECOVERY can't happen until the old server has
released /dev/ublkcN), and say so in the changelog.
> > - io->flags &= ~UBLK_IO_FLAG_CANCELED;
> > ublk_fill_io_cmd(io, cmd);
> > + spin_unlock(&ubq->cancel_lock);
>
> And this looks like it may also duplicate
> https://lore.kernel.org/linux-block/20260508123746.242018-1-tom.leiming@gmail.com/
That one is f7700a4415af ("ublk: fix use-after-free in
ublk_cancel_cmd()"), and it orders ublk_cancel_cmd() against the reset
path, ublk_reset_ch_dev() clearing io->cmd. This hunk is the fetch side,
which f7700a4415af left alone: __ublk_fetch() drops the stale
UBLK_IO_FLAG_CANCELED and then publishes io->cmd. A cancel that runs in
between sets CANCELED again, the fetch clears it, and the queue goes
ready over a command that was already completed. Same lock, different
race. I'll mention f7700a4415af in the changelog so that's clearer. Thanks,
Josef
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 2/9] ublk: clear ub->canceling with the queue's own flag
2026-09-28 16:00 [PATCH 0/9] ublk: fix dispatch to canceled io commands Josef Bacik
2026-09-28 16:00 ` [PATCH 1/9] ublk: keep queue canceling over canceled commands Josef Bacik
@ 2026-09-28 16:00 ` Josef Bacik
2026-09-28 16:00 ` [PATCH 3/9] ublk: publish io->cmd under io->lock in the commit paths Josef Bacik
` (7 subsequent siblings)
9 siblings, 0 replies; 17+ messages in thread
From: Josef Bacik @ 2026-09-28 16:00 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Caleb Sander Mateos
Cc: linux-block, linux-kernel, linux-doc, Josef Bacik
ublk_queue_rq() dispatches a request to a NULL io->cmd and the kernel
oopses when, during recovery, the task serving a queue which is ready
already exits before the last queue is ready. ublk_start_cancel() skips
marking and quiescing the queues while ub->canceling is set, since that
means every queue is marked. Since commit 3f3850785594 ("ublk: fix
batch I/O recovery -ENODEV error") it does not mean that any more:
queue 0 gets ready: ubq->canceling = false
ub->canceling stays set until the last queue
is ready
queue 0 task exits: ublk_start_cancel() sees ub->canceling, returns
queue 0 commands done, ubq->canceling unset
The disk is attached during recovery, and on a queue which got ready
only ->canceling holds requests back, so the next request for queue 0
is dispatched.
Fix by clearing ub->canceling together with the queue flag, under
cancel_mutex, so that the next cancel marks and quiesces every queue
again, and by dropping the clearing on full readiness.
Fixes: 3f3850785594 ("ublk: fix batch I/O recovery -ENODEV error")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
drivers/block/ublk_drv.c | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 183de080e008..50c99b28ef21 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -3049,14 +3049,21 @@ static bool ublk_queue_has_canceled_io(const struct ublk_queue *ubq)
/* reset per-queue io flags */
static void ublk_queue_reset_io_flags(struct ublk_queue *ubq)
{
+ struct ublk_device *ub = ubq->dev;
+
+ mutex_lock(&ub->cancel_mutex);
spin_lock(&ubq->cancel_lock);
/*
* A command canceled after being fetched is still counted as ready
* but can't take requests, so the queue has to stay canceling.
*/
- if (!ublk_queue_has_canceled_io(ubq))
+ if (!ublk_queue_has_canceled_io(ubq)) {
ubq->canceling = false;
+ /* not every queue is marked now, let the next cancel redo it */
+ ub->canceling = false;
+ }
spin_unlock(&ubq->cancel_lock);
+ mutex_unlock(&ub->cancel_mutex);
ubq->fail_io = false;
ubq->force_abort = false;
}
@@ -3084,17 +3091,9 @@ static void ublk_mark_io_ready(struct ublk_device *ub, u16 q_id)
ublk_queue_reset_io_flags(ubq);
}
- /* Check if all queues are ready */
- if (ublk_dev_ready(ub)) {
- /*
- * All queues ready - clear device-level canceling flag
- * and wake ublk_dev_ready() waiters.
- */
- mutex_lock(&ub->cancel_mutex);
- ub->canceling = false;
- mutex_unlock(&ub->cancel_mutex);
+ /* All queues ready - wake ublk_dev_ready() waiters */
+ if (ublk_dev_ready(ub))
wake_up_var(&ub->nr_queue_ready);
- }
}
static inline int ublk_check_cmd_op(u32 cmd_op)
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 3/9] ublk: publish io->cmd under io->lock in the commit paths
2026-09-28 16:00 [PATCH 0/9] ublk: fix dispatch to canceled io commands Josef Bacik
2026-09-28 16:00 ` [PATCH 1/9] ublk: keep queue canceling over canceled commands Josef Bacik
2026-09-28 16:00 ` [PATCH 2/9] ublk: clear ub->canceling with the queue's own flag Josef Bacik
@ 2026-09-28 16:00 ` Josef Bacik
2026-09-28 17:53 ` Caleb Sander Mateos
2026-09-28 16:00 ` [PATCH 4/9] ublk: read the io under io->lock in ublk_cancel_cmd() Josef Bacik
` (6 subsequent siblings)
9 siblings, 1 reply; 17+ messages in thread
From: Josef Bacik @ 2026-09-28 16:00 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Caleb Sander Mateos
Cc: linux-block, linux-kernel, linux-doc, Josef Bacik
io->cmd shares its storage with io->req. FETCH, COMMIT_AND_FETCH and
NEED_GET_DATA switch the io from one to the other in
ublk_fill_io_cmd() without a lock of their own. That was fine as long
as everything else looking at io->cmd ran under the uring_lock of the
same ring, which is the case for the io_uring cancel callback.
ublk_cancel_cmd() is also called from the control path, from
ublk_cancel_dev() with IO_URING_F_UNLOCKED, and the next patches add
more such callers which run while the server still commits requests.
Nothing orders their reads of io->flags and io->cmd against
ublk_fill_io_cmd() then. A reader can find UBLK_IO_FLAG_ACTIVE set and
still pick up the request pointer from the union.
The batch commit path calls ublk_fill_io_cmd() under io->lock already.
Do the same in the other three callers, so that io->lock covers every
switch of the union to a command. io->lock is per io and only taken by
the task which commits that io, so the fast path gains an uncontended
lock in a cacheline it writes anyway.
ublk_batch_prep_io() calls __ublk_fetch() with io->lock held, which
makes the order io->lock, then ubq->cancel_lock.
The readers move under io->lock in the next patch.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
drivers/block/ublk_drv.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 50c99b28ef21..17a33539f271 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -3169,6 +3169,9 @@ ublk_fill_io_cmd(struct ublk_io *io, struct io_uring_cmd *cmd)
{
struct request *req = io->req;
+ /* io->cmd shares its storage with io->req, switch them under io->lock */
+ lockdep_assert_held(&io->lock);
+
io->cmd = cmd;
io->flags |= UBLK_IO_FLAG_ACTIVE;
/* now this cmd slot is owned by ublk driver */
@@ -3338,8 +3341,11 @@ static int ublk_fetch(struct io_uring_cmd *cmd, struct ublk_device *ub,
*/
mutex_lock(&ub->mutex);
ret = ublk_validate_io_buf(ub, cmd, &auto_buf);
- if (!ret)
+ if (!ret) {
+ ublk_io_lock(io);
ret = __ublk_fetch(cmd, ub, io, q_id);
+ ublk_io_unlock(io);
+ }
if (!ret) {
ublk_apply_io_buf(ub, io, cmd, buf_addr, &auto_buf, NULL);
ublk_mark_io_ready(ub, q_id);
@@ -3496,7 +3502,9 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd,
if (ret)
goto out;
io->res = result;
+ ublk_io_lock(io);
req = ublk_fill_io_cmd(io, cmd);
+ ublk_io_unlock(io);
ublk_apply_io_buf(ub, io, cmd, addr, &auto_buf, &buf_idx);
if (buf_idx != UBLK_INVALID_BUF_IDX)
io_buffer_unregister(cmd, buf_idx, issue_flags);
@@ -3514,7 +3522,9 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd,
* uring_cmd active first and prepare for handling new requeued
* request
*/
+ ublk_io_lock(io);
req = ublk_fill_io_cmd(io, cmd);
+ ublk_io_unlock(io);
io->buf.addr = addr;
if (likely(ublk_get_data(ubq, io, req))) {
__ublk_prep_compl_io_cmd(io, req);
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 3/9] ublk: publish io->cmd under io->lock in the commit paths
2026-09-28 16:00 ` [PATCH 3/9] ublk: publish io->cmd under io->lock in the commit paths Josef Bacik
@ 2026-09-28 17:53 ` Caleb Sander Mateos
2026-09-29 13:06 ` Josef Bacik
0 siblings, 1 reply; 17+ messages in thread
From: Caleb Sander Mateos @ 2026-09-28 17:53 UTC (permalink / raw)
To: Josef Bacik; +Cc: Ming Lei, Jens Axboe, linux-block, linux-kernel, linux-doc
On Mon, Sep 28, 2026 at 9:03 AM Josef Bacik <josef@toxicpanda.com> wrote:
>
> io->cmd shares its storage with io->req. FETCH, COMMIT_AND_FETCH and
> NEED_GET_DATA switch the io from one to the other in
> ublk_fill_io_cmd() without a lock of their own. That was fine as long
> as everything else looking at io->cmd ran under the uring_lock of the
> same ring, which is the case for the io_uring cancel callback.
>
> ublk_cancel_cmd() is also called from the control path, from
> ublk_cancel_dev() with IO_URING_F_UNLOCKED, and the next patches add
> more such callers which run while the server still commits requests.
> Nothing orders their reads of io->flags and io->cmd against
> ublk_fill_io_cmd() then. A reader can find UBLK_IO_FLAG_ACTIVE set and
> still pick up the request pointer from the union.
>
> The batch commit path calls ublk_fill_io_cmd() under io->lock already.
> Do the same in the other three callers, so that io->lock covers every
> switch of the union to a command. io->lock is per io and only taken by
> the task which commits that io, so the fast path gains an uncontended
> lock in a cacheline it writes anyway.
>
> ublk_batch_prep_io() calls __ublk_fetch() with io->lock held, which
> makes the order io->lock, then ubq->cancel_lock.
>
> The readers move under io->lock in the next patch.
>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
> ---
> drivers/block/ublk_drv.c | 12 +++++++++++-
> 1 file changed, 11 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
> index 50c99b28ef21..17a33539f271 100644
> --- a/drivers/block/ublk_drv.c
> +++ b/drivers/block/ublk_drv.c
> @@ -3169,6 +3169,9 @@ ublk_fill_io_cmd(struct ublk_io *io, struct io_uring_cmd *cmd)
> {
> struct request *req = io->req;
>
> + /* io->cmd shares its storage with io->req, switch them under io->lock */
> + lockdep_assert_held(&io->lock);
> +
> io->cmd = cmd;
> io->flags |= UBLK_IO_FLAG_ACTIVE;
> /* now this cmd slot is owned by ublk driver */
> @@ -3338,8 +3341,11 @@ static int ublk_fetch(struct io_uring_cmd *cmd, struct ublk_device *ub,
> */
> mutex_lock(&ub->mutex);
> ret = ublk_validate_io_buf(ub, cmd, &auto_buf);
> - if (!ret)
> + if (!ret) {
> + ublk_io_lock(io);
> ret = __ublk_fetch(cmd, ub, io, q_id);
> + ublk_io_unlock(io);
> + }
> if (!ret) {
> ublk_apply_io_buf(ub, io, cmd, buf_addr, &auto_buf, NULL);
> ublk_mark_io_ready(ub, q_id);
> @@ -3496,7 +3502,9 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd,
> if (ret)
> goto out;
> io->res = result;
> + ublk_io_lock(io);
> req = ublk_fill_io_cmd(io, cmd);
> + ublk_io_unlock(io);
Taking a spinlock for every ublk I/O completion will be very
expensive. Is it not possible all paths calling ublk_cancel_dev() to
wait for all tags to go idle?
Best,
Caleb
> ublk_apply_io_buf(ub, io, cmd, addr, &auto_buf, &buf_idx);
> if (buf_idx != UBLK_INVALID_BUF_IDX)
> io_buffer_unregister(cmd, buf_idx, issue_flags);
> @@ -3514,7 +3522,9 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd,
> * uring_cmd active first and prepare for handling new requeued
> * request
> */
> + ublk_io_lock(io);
> req = ublk_fill_io_cmd(io, cmd);
> + ublk_io_unlock(io);
> io->buf.addr = addr;
> if (likely(ublk_get_data(ubq, io, req))) {
> __ublk_prep_compl_io_cmd(io, req);
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 3/9] ublk: publish io->cmd under io->lock in the commit paths
2026-09-28 17:53 ` Caleb Sander Mateos
@ 2026-09-29 13:06 ` Josef Bacik
0 siblings, 0 replies; 17+ messages in thread
From: Josef Bacik @ 2026-09-29 13:06 UTC (permalink / raw)
To: Caleb Sander Mateos
Cc: Ming Lei, Jens Axboe, linux-block, linux-kernel, linux-doc
On Mon, Sep 28, 2026 at 10:53:38AM -0700, Caleb Sander Mateos wrote:
> On Mon, Sep 28, 2026 at 9:03 AM Josef Bacik <josef@toxicpanda.com> wrote:
> > + ublk_io_lock(io);
> > req = ublk_fill_io_cmd(io, cmd);
> > + ublk_io_unlock(io);
>
> Taking a spinlock for every ublk I/O completion will be very
> expensive. Is it not possible all paths calling ublk_cancel_dev() to
> wait for all tags to go idle?
I measured it on a c6id.metal (Xeon 8375C) under KVM: an 8 vCPU guest,
kublk null target with 4 queues, fio 4k randread with 4 jobs at iodepth
32, ten interleaved rounds of three kernels: for-next, this series, and
this series with io->lock taken back out of the commit path. That last
one also drops the second lock/unlock pair patch 5 adds after
ublk_prep_cancel(), so it isolates both. The guests weren't pinned and
landed at two throughput levels about 15% apart, so I compared within a
level.
Series against the no-lock kernel, IOPS / CPU time per I/O:
plain -0.04% / +0.7% (high level) -0.7% / +0.7% (low level)
zero copy -0.6% / +1.1% +1.5% / -1.2%
Batch mode, which runs the same per-I/O code on both kernels, differs by
-0.03% / +1.2% and +0.4% / +0.5%, so the lock is inside the noise of
this setup, which is under 1% of about 3.5us per I/O. Against for-next
the series is +0.4% IOPS / +1.0% CPU per I/O in plain mode at the high
level. I'm rerunning with pinned guests to tighten that and will follow
up if it moves.
It's one lock per io that only the task committing that io takes, so
it's uncontended, which fits those numbers.
Waiting for the tags to go idle doesn't close the race this is for,
though. The control path claims io->cmd while the server can still
commit on the same io, and what matters is ordering the claim against
the commit switching the io from the request to the new command. Idle
doesn't give you that: a tag can be idle when you look and be re-armed
by a COMMIT_AND_FETCH right after. That's the QUIESCE_DEV hang on
for-next today, its cancel pass skips a tag whose request is with the
server, the server commits and re-arms it, and nothing ever completes
that command.
Also, ublk_wait_for_idle_io() can't actually wait as it is.
blk_mq_tagset_busy_iter() only visits started requests and
ublk_count_busy_req() only counts requests that aren't started, so the
count is always 0. I'll send a fix for that separately with the
QUIESCE_DEV work.
If the numbers show a real cost I'd rather find a way to keep the lock
off the fast path than lose the ordering, so I'm open to ideas.
Thanks,
Josef
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 4/9] ublk: read the io under io->lock in ublk_cancel_cmd()
2026-09-28 16:00 [PATCH 0/9] ublk: fix dispatch to canceled io commands Josef Bacik
` (2 preceding siblings ...)
2026-09-28 16:00 ` [PATCH 3/9] ublk: publish io->cmd under io->lock in the commit paths Josef Bacik
@ 2026-09-28 16:00 ` Josef Bacik
2026-09-28 16:00 ` [PATCH 5/9] ublk: complete a command canceled before it was marked from its issuer Josef Bacik
` (5 subsequent siblings)
9 siblings, 0 replies; 17+ messages in thread
From: Josef Bacik @ 2026-09-28 16:00 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Caleb Sander Mateos
Cc: linux-block, linux-kernel, linux-doc, Josef Bacik
ublk_cancel_cmd() looks at three things before it takes a command: the
io is active, the request of its tag is not started, and the io was not
canceled before. Only the last one is read under a lock.
Since the previous patch ublk_fill_io_cmd() sets io->cmd and
UBLK_IO_FLAG_ACTIVE under io->lock. Take io->lock in ublk_cancel_cmd()
around all three checks and the read of io->cmd, so that a cancel from
the control path either finds the io not active or finds the command
which was stored with the flag.
The request check belongs in the same section. COMMIT_AND_FETCH ends
the request it commits after it has stored the new command. A cancel
which finds that request idle holds io->lock at that point, so it runs
after ublk_fill_io_cmd() dropped it and reads the new command, not the
request pointer which was in the union before.
Requests cannot be dispatched to the io meanwhile, every caller has the
queue marked as canceling or the disk deleted, so the dispatch side does
not need the lock.
ubq->cancel_lock nests inside of io->lock, as it does for
ublk_batch_prep_io() already.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
drivers/block/ublk_drv.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 17a33539f271..1455ce27d3d1 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -2777,10 +2777,16 @@ static void ublk_cancel_cmd(struct ublk_queue *ubq, u16 tag,
struct ublk_device *ub = ubq->dev;
struct io_uring_cmd *cmd = NULL;
struct request *req;
- bool done;
+ /*
+ * ublk_fill_io_cmd() runs under io->lock, so either the io is not
+ * active yet or its command is in io->cmd. The request the command
+ * was committed for is ended after that, check it in here as well, so
+ * that one seen idle goes with the command fetched for the next one.
+ */
+ ublk_io_lock(io);
if (!(io->flags & UBLK_IO_FLAG_ACTIVE))
- return;
+ goto unlock;
/*
* Don't try to cancel this command if the request is started for
@@ -2794,18 +2800,19 @@ static void ublk_cancel_cmd(struct ublk_queue *ubq, u16 tag,
*/
req = blk_mq_tag_to_rq(ub->tag_set.tags[ubq->q_id], tag);
if (req && blk_mq_request_started(req) && req->tag == tag)
- return;
+ goto unlock;
spin_lock(&ubq->cancel_lock);
- done = !!(io->flags & UBLK_IO_FLAG_CANCELED);
- if (!done) {
+ if (!(io->flags & UBLK_IO_FLAG_CANCELED)) {
io->flags |= UBLK_IO_FLAG_CANCELED;
cmd = io->cmd;
io->cmd = NULL;
}
spin_unlock(&ubq->cancel_lock);
+unlock:
+ ublk_io_unlock(io);
- if (!done && cmd)
+ if (cmd)
io_uring_cmd_done(cmd, UBLK_IO_RES_ABORT, issue_flags);
}
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 5/9] ublk: complete a command canceled before it was marked from its issuer
2026-09-28 16:00 [PATCH 0/9] ublk: fix dispatch to canceled io commands Josef Bacik
` (3 preceding siblings ...)
2026-09-28 16:00 ` [PATCH 4/9] ublk: read the io under io->lock in ublk_cancel_cmd() Josef Bacik
@ 2026-09-28 16:00 ` Josef Bacik
2026-09-28 16:00 ` [PATCH 6/9] ublk: split ublk_claim_cmd() out of ublk_cancel_cmd() Josef Bacik
` (4 subsequent siblings)
9 siblings, 0 replies; 17+ messages in thread
From: Josef Bacik @ 2026-09-28 16:00 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Caleb Sander Mateos
Cc: linux-block, linux-kernel, linux-doc, Josef Bacik
The issue paths store the command in io->cmd first and call
ublk_prep_cancel() on their way out, which is where io_uring puts the
command on its list of cancelable commands. The io_uring cancel callback
only ever sees commands from that list. A cancel from the control path
finds the command in io->cmd as soon as it is stored, and can complete it
before it is marked:
issue path control path
ublk_fill_io_cmd()
ublk_cancel_cmd()
io_uring_cmd_done()
not cancelable, nothing to remove
ublk_prep_cancel()
io_uring_cmd_mark_cancelable()
adds the completed request to the list
Leaving such a command alone is no better. FETCH, COMMIT_AND_FETCH and
NEED_GET_DATA queue it right after, no cancel may come after the one
which skipped it, and a server waits for all its commands before it
exits.
Fix by taking the command off the io all the same in ublk_cancel_cmd(),
but setting UBLK_IO_FLAG_CANCEL_DEFERRED instead of completing it, and
by having the issue path look for that flag under io->lock once
ublk_prep_cancel() has marked the command, and complete the command with
UBLK_IO_RES_ABORT then. A cancel which takes io->lock after that sees
the marking and completes the command itself.
IORING_URING_CMD_CANCELABLE is not one of the flags the server passes in
the SQE. It is a bit io_uring keeps for itself in the same word of its
own copy of the command: io_uring_cmd_mark_cancelable() sets it and
io_uring_cmd_done() clears it, both with uring_lock held. The control
path does not hold uring_lock, so read the word with READ_ONCE().
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
drivers/block/ublk_drv.c | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 1455ce27d3d1..2ff6506b8664 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -191,6 +191,14 @@ struct ublk_batch_io_data {
/* atomic RW with ubq->cancel_lock */
#define UBLK_IO_FLAG_CANCELED 0x80000000
+/*
+ * Set next to UBLK_IO_FLAG_CANCELED by a cancel which took a command
+ * before io_uring marked it cancelable, the issue path completes the
+ * command then, see ublk_take_canceled_cmd(). Set and cleared under
+ * io->lock.
+ */
+#define UBLK_IO_FLAG_CANCEL_DEFERRED 0x40000000
+
/*
* Initialize refcount to a large number to include any registered buffers.
* UBLK_IO_COMMIT_AND_FETCH_REQ will release these references minus those for
@@ -2807,6 +2815,17 @@ static void ublk_cancel_cmd(struct ublk_queue *ubq, u16 tag,
io->flags |= UBLK_IO_FLAG_CANCELED;
cmd = io->cmd;
io->cmd = NULL;
+ /*
+ * The issue path stores the command before ublk_prep_cancel()
+ * marks it cancelable, and completing it in between would
+ * leave a completed request on io_uring's list of cancelable
+ * commands. Leave the completion to the issue path then, it
+ * looks for this once the command is marked.
+ */
+ if (!(READ_ONCE(cmd->flags) & IORING_URING_CMD_CANCELABLE)) {
+ io->flags |= UBLK_IO_FLAG_CANCEL_DEFERRED;
+ cmd = NULL;
+ }
}
spin_unlock(&ubq->cancel_lock);
unlock:
@@ -3202,6 +3221,24 @@ static inline void ublk_prep_cancel(struct io_uring_cmd *cmd,
io_uring_cmd_mark_cancelable(cmd, issue_flags);
}
+/*
+ * Called by the issue path after ublk_prep_cancel(): a cancel which found
+ * the command before it was marked took it off the io and left completing
+ * it here. io->lock orders this against ublk_cancel_cmd(), which sees the
+ * marking if it runs after this, and completes the command itself then.
+ */
+static bool ublk_take_canceled_cmd(struct ublk_io *io)
+{
+ bool canceled;
+
+ ublk_io_lock(io);
+ canceled = io->flags & UBLK_IO_FLAG_CANCEL_DEFERRED;
+ io->flags &= ~UBLK_IO_FLAG_CANCEL_DEFERRED;
+ ublk_io_unlock(io);
+
+ return canceled;
+}
+
static void ublk_io_release(void *priv)
{
struct request *rq = priv;
@@ -3466,6 +3503,8 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd,
goto out;
ublk_prep_cancel(cmd, issue_flags, ubq, tag);
+ if (ublk_take_canceled_cmd(io))
+ io_uring_cmd_done(cmd, UBLK_IO_RES_ABORT, issue_flags);
return -EIOCBQUEUED;
}
@@ -3542,6 +3581,8 @@ static int ublk_ch_uring_cmd_local(struct io_uring_cmd *cmd,
goto out;
}
ublk_prep_cancel(cmd, issue_flags, ubq, tag);
+ if (ublk_take_canceled_cmd(io))
+ io_uring_cmd_done(cmd, UBLK_IO_RES_ABORT, issue_flags);
return -EIOCBQUEUED;
out:
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 6/9] ublk: split ublk_claim_cmd() out of ublk_cancel_cmd()
2026-09-28 16:00 [PATCH 0/9] ublk: fix dispatch to canceled io commands Josef Bacik
` (4 preceding siblings ...)
2026-09-28 16:00 ` [PATCH 5/9] ublk: complete a command canceled before it was marked from its issuer Josef Bacik
@ 2026-09-28 16:00 ` Josef Bacik
2026-09-28 16:00 ` [PATCH 7/9] ublk: mark queues and command in one cancel_mutex hold Josef Bacik
` (3 subsequent siblings)
9 siblings, 0 replies; 17+ messages in thread
From: Josef Bacik @ 2026-09-28 16:00 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Caleb Sander Mateos
Cc: linux-block, linux-kernel, linux-doc, Josef Bacik
ublk_cancel_cmd() marks the io as canceled, takes the command off it and
completes the command. The next patches have callers which do the first
two steps under a mutex and must not complete the command there: the
control paths complete with IO_URING_F_UNLOCKED, where
io_uring_cmd_done() takes uring_lock, and ublk_fetch() takes ub->mutex
and cancel_mutex inside of uring_lock.
Move the marking into ublk_claim_cmd(), which returns the command for the
caller to complete. ublk_cancel_cmd() is ublk_claim_cmd() followed by
io_uring_cmd_done().
No functional change.
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
drivers/block/ublk_drv.c | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 2ff6506b8664..99ac56d36dd0 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -2778,8 +2778,11 @@ static void ublk_start_cancel(struct ublk_device *ub)
ublk_put_disk(disk);
}
-static void ublk_cancel_cmd(struct ublk_queue *ubq, u16 tag,
- unsigned int issue_flags)
+/*
+ * Mark a fetched command as canceled and take it off the io, the caller
+ * completes it
+ */
+static struct io_uring_cmd *ublk_claim_cmd(struct ublk_queue *ubq, u16 tag)
{
struct ublk_io *io = &ubq->ios[tag];
struct ublk_device *ub = ubq->dev;
@@ -2831,6 +2834,14 @@ static void ublk_cancel_cmd(struct ublk_queue *ubq, u16 tag,
unlock:
ublk_io_unlock(io);
+ return cmd;
+}
+
+static void ublk_cancel_cmd(struct ublk_queue *ubq, u16 tag,
+ unsigned int issue_flags)
+{
+ struct io_uring_cmd *cmd = ublk_claim_cmd(ubq, tag);
+
if (cmd)
io_uring_cmd_done(cmd, UBLK_IO_RES_ABORT, issue_flags);
}
@@ -3224,7 +3235,7 @@ static inline void ublk_prep_cancel(struct io_uring_cmd *cmd,
/*
* Called by the issue path after ublk_prep_cancel(): a cancel which found
* the command before it was marked took it off the io and left completing
- * it here. io->lock orders this against ublk_cancel_cmd(), which sees the
+ * it here. io->lock orders this against ublk_claim_cmd(), which sees the
* marking if it runs after this, and completes the command itself then.
*/
static bool ublk_take_canceled_cmd(struct ublk_io *io)
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 7/9] ublk: mark queues and command in one cancel_mutex hold
2026-09-28 16:00 [PATCH 0/9] ublk: fix dispatch to canceled io commands Josef Bacik
` (5 preceding siblings ...)
2026-09-28 16:00 ` [PATCH 6/9] ublk: split ublk_claim_cmd() out of ublk_cancel_cmd() Josef Bacik
@ 2026-09-28 16:00 ` Josef Bacik
2026-09-28 16:00 ` [PATCH 8/9] ublk: claim commands under ub->mutex in ublk_stop_dev() Josef Bacik
` (2 subsequent siblings)
9 siblings, 0 replies; 17+ messages in thread
From: Josef Bacik @ 2026-09-28 16:00 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Caleb Sander Mateos
Cc: linux-block, linux-kernel, linux-doc, Josef Bacik
ublk_queue_rq() dispatches a request to a NULL io->cmd and the kernel
oopses when the last FETCH of a queue runs in the middle of
ublk_uring_cmd_cancel_fn(), which drops cancel_mutex between marking
the queues and marking the command:
cancel callback last FETCH of the queue
ublk_start_cancel()
queues marked canceling
cancel_mutex dropped
ublk_mark_io_ready()
queue is ready
no canceled command found
->canceling = false
ublk_cancel_cmd()
UBLK_IO_FLAG_CANCELED set
io->cmd = NULL, command done
ublk_queue_reset_io_flags() takes cancel_mutex for the ready
transition, so once the callback holds it across both steps the
transition comes either before or after them.
Move the locking of cancel_mutex from ublk_start_cancel() to its two
callers, and have ublk_uring_cmd_cancel_fn() claim the command with
ublk_claim_cmd() before it drops the mutex. The command is completed
after the unlock.
ublk_start_cancel() holds a reference on the disk, and dropping it used
to come after the unlock. Keep it that way by returning the reference to
the caller, the last put runs disk_release() and that should not happen
under cancel_mutex.
Fixes: 728cbac5fe21 ("ublk: move device reset into ublk_ch_release()")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
drivers/block/ublk_drv.c | 39 +++++++++++++++++++++++++++++++--------
1 file changed, 31 insertions(+), 8 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 99ac56d36dd0..18046e2bf753 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -2748,11 +2748,15 @@ static void ublk_abort_queue(struct ublk_device *ub, struct ublk_queue *ubq)
ublk_abort_batch_queue(ub, ubq);
}
-static void ublk_start_cancel(struct ublk_device *ub)
+/*
+ * Returns the disk reference it took. The caller drops it after
+ * cancel_mutex, the last put ends up in disk_release().
+ */
+static struct gendisk *ublk_start_cancel(struct ublk_device *ub)
+ __must_hold(&ub->cancel_mutex)
{
struct gendisk *disk = ublk_get_disk(ub);
- mutex_lock(&ub->cancel_mutex);
if (ub->canceling)
goto out;
@@ -2774,8 +2778,7 @@ static void ublk_start_cancel(struct ublk_device *ub)
ublk_set_canceling(ub, true);
}
out:
- mutex_unlock(&ub->cancel_mutex);
- ublk_put_disk(disk);
+ return disk;
}
/*
@@ -2900,8 +2903,13 @@ static void ublk_batch_cancel_fn(struct io_uring_cmd *cmd,
struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(cmd);
struct ublk_batch_fetch_cmd *fcmd = pdu->fcmd;
struct ublk_queue *ubq = pdu->ubq;
+ struct ublk_device *ub = ubq->dev;
+ struct gendisk *disk;
- ublk_start_cancel(ubq->dev);
+ mutex_lock(&ub->cancel_mutex);
+ disk = ublk_start_cancel(ub);
+ mutex_unlock(&ub->cancel_mutex);
+ ublk_put_disk(disk);
ublk_batch_cancel_cmd(ubq, fcmd, issue_flags);
}
@@ -2926,7 +2934,10 @@ static void ublk_uring_cmd_cancel_fn(struct io_uring_cmd *cmd,
{
struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(cmd);
struct ublk_queue *ubq = pdu->ubq;
+ struct io_uring_cmd *claimed;
struct task_struct *task;
+ struct ublk_device *ub;
+ struct gendisk *disk;
struct ublk_io *io;
if (WARN_ON_ONCE(!ubq))
@@ -2935,15 +2946,27 @@ static void ublk_uring_cmd_cancel_fn(struct io_uring_cmd *cmd,
if (WARN_ON_ONCE(pdu->tag >= ubq->q_depth))
return;
+ ub = ubq->dev;
task = io_uring_cmd_get_task(cmd);
io = &ubq->ios[pdu->tag];
if (WARN_ON_ONCE(task && task != io->task))
return;
- ublk_start_cancel(ubq->dev);
-
+ /*
+ * Mark the queues and the command in one cancel_mutex section, so
+ * that a queue getting ready meantime either finds the command
+ * canceled or clears ->canceling first and gets marked again here.
+ * Complete it outside, ublk_claim_cmd()'s other callers have to.
+ */
+ mutex_lock(&ub->cancel_mutex);
+ disk = ublk_start_cancel(ub);
WARN_ON_ONCE(io->cmd != cmd);
- ublk_cancel_cmd(ubq, pdu->tag, issue_flags);
+ claimed = ublk_claim_cmd(ubq, pdu->tag);
+ mutex_unlock(&ub->cancel_mutex);
+ ublk_put_disk(disk);
+
+ if (claimed)
+ io_uring_cmd_done(claimed, UBLK_IO_RES_ABORT, issue_flags);
}
static inline bool ublk_queue_ready(const struct ublk_queue *ubq)
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 8/9] ublk: claim commands under ub->mutex in ublk_stop_dev()
2026-09-28 16:00 [PATCH 0/9] ublk: fix dispatch to canceled io commands Josef Bacik
` (6 preceding siblings ...)
2026-09-28 16:00 ` [PATCH 7/9] ublk: mark queues and command in one cancel_mutex hold Josef Bacik
@ 2026-09-28 16:00 ` Josef Bacik
2026-09-28 16:00 ` [PATCH 9/9] ublk: refuse to go live over canceled io commands Josef Bacik
2026-09-29 14:44 ` [PATCH 0/9] ublk: fix dispatch to " Ming Lei
9 siblings, 0 replies; 17+ messages in thread
From: Josef Bacik @ 2026-09-28 16:00 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Caleb Sander Mateos
Cc: linux-block, linux-kernel, linux-doc, Josef Bacik
STOP_DEV followed by START_DEV on a device which is ready but was never
started oopses on the first read, which reaches ublk_queue_cmd() with a
NULL io->cmd. ublk_stop_dev() completes the fetched commands through
ublk_cancel_dev() after dropping ub->mutex, which is only safe once
del_gendisk() has returned:
STOP_DEV on a ready device without a disk
ublk_stop_dev_unlocked() returns early, state is UBLK_S_DEV_DEAD
ublk_cancel_dev() completes the commands
->canceling is never set, the device still counts as ready
START_DEV
passes every check, adds the disk, schedules the partition scan
START_DEV can also take ub->mutex right after ublk_stop_dev() drops it.
The idle commands of a live disk are canceled then with nothing
serializing that against ublk_queue_rq(). Commit 1133b93fc7f6 ("ublk:
set canceling flag even when disk is not allocated") closed the same
window for the io_uring cancel callbacks and left the STOP_DEV side
open.
START_DEV has to take ub->mutex before it can add a disk, so once the
commands are claimed under that mutex a queue getting ready afterwards
finds them canceled and stays canceling. Cancel the partition scan work
before dropping ub->mutex too, so that a START_DEV right after the
unlock does not get its new scan canceled.
ublk_cancel_dev() runs without ub->mutex, after this pass and after
QUIESCE_DEV, and a queue which is no longer canceling then belongs to a
round which started after the marking. So far it completed the new
commands of such a queue too, for a QUIESCE_DEV sent to a quiesced
device while the queue was already taking requests. Decide once under
cancel_mutex whether the queue is still canceling, take its commands in
that section, the fetch commands of a UBLK_F_BATCH_IO queue as well, and
complete them after it, as with the per-io commands. The fetch commands
stay linked on the list they are moved to, so take each one off it under
evts_lock before completing it, as ublk_batch_cancel_cmd() does, since
the cancel callback of their ring may take one first.
Fix by marking the queues as canceling and claiming their fetched
commands while ub->mutex is still held, and completing them once it is
dropped.
Fixes: 85248d670b71 ("ublk: move ublk_cancel_dev() out of ub->mutex")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
drivers/block/ublk_drv.c | 145 ++++++++++++++++++++++++++++++++++++++---------
1 file changed, 119 insertions(+), 26 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 18046e2bf753..5e86405b294b 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -129,6 +129,8 @@ struct ublk_uring_cmd_pdu {
union {
struct request *req;
struct request *req_list;
+ /* links commands claimed by ublk_claim_queue_cmds() */
+ struct io_uring_cmd *next_claimed;
};
/*
@@ -2417,7 +2419,7 @@ static void ublk_reset_ch_dev(struct ublk_device *ub)
for (i = 0; i < ub->dev_info.nr_hw_queues; i++) {
struct ublk_queue *ubq = ublk_get_queue(ub, i);
- /* Sync with ublk_cancel_cmd() */
+ /* Sync with ublk_claim_cmd() */
spin_lock(&ubq->cancel_lock);
ublk_queue_reinit(ub, ubq);
spin_unlock(&ubq->cancel_lock);
@@ -2840,15 +2842,6 @@ static struct io_uring_cmd *ublk_claim_cmd(struct ublk_queue *ubq, u16 tag)
return cmd;
}
-static void ublk_cancel_cmd(struct ublk_queue *ubq, u16 tag,
- unsigned int issue_flags)
-{
- struct io_uring_cmd *cmd = ublk_claim_cmd(ubq, tag);
-
- if (cmd)
- io_uring_cmd_done(cmd, UBLK_IO_RES_ABORT, issue_flags);
-}
-
/*
* Cancel a batch fetch command if it hasn't been claimed by another path.
*
@@ -2877,23 +2870,45 @@ static void ublk_batch_cancel_cmd(struct ublk_queue *ubq,
}
}
-static void ublk_batch_cancel_queue(struct ublk_queue *ubq)
+/*
+ * Move the parked fetch commands of a batch queue to @fcmd_list, for
+ * ublk_batch_complete_fcmds(); the active one is left to its dispatcher.
+ * They stay linked, and the cancel callback of their ring may still take
+ * one off @fcmd_list first: whoever unlinks a command under evts_lock
+ * completes it.
+ */
+static void ublk_batch_claim_fcmds(struct ublk_queue *ubq,
+ struct list_head *fcmd_list)
{
struct ublk_batch_fetch_cmd *fcmd;
- LIST_HEAD(fcmd_list);
spin_lock(&ubq->evts_lock);
ubq->force_abort = true;
- list_splice_init(&ubq->fcmd_head, &fcmd_list);
+ list_splice_init(&ubq->fcmd_head, fcmd_list);
fcmd = READ_ONCE(ubq->active_fcmd);
if (fcmd)
list_move(&fcmd->node, &ubq->fcmd_head);
spin_unlock(&ubq->evts_lock);
+}
- while (!list_empty(&fcmd_list)) {
- fcmd = list_first_entry(&fcmd_list,
- struct ublk_batch_fetch_cmd, node);
- ublk_batch_cancel_cmd(ubq, fcmd, IO_URING_F_UNLOCKED);
+static void ublk_batch_complete_fcmds(struct ublk_queue *ubq,
+ struct list_head *fcmd_list)
+{
+ struct ublk_batch_fetch_cmd *fcmd;
+
+ for (;;) {
+ spin_lock(&ubq->evts_lock);
+ fcmd = list_first_entry_or_null(fcmd_list,
+ struct ublk_batch_fetch_cmd, node);
+ if (fcmd)
+ list_del_init(&fcmd->node);
+ spin_unlock(&ubq->evts_lock);
+ if (!fcmd)
+ break;
+
+ io_uring_cmd_done(fcmd->cmd, UBLK_IO_RES_ABORT,
+ IO_URING_F_UNLOCKED);
+ ublk_batch_free_fcmd(fcmd);
}
}
@@ -2960,7 +2975,8 @@ static void ublk_uring_cmd_cancel_fn(struct io_uring_cmd *cmd,
*/
mutex_lock(&ub->cancel_mutex);
disk = ublk_start_cancel(ub);
- WARN_ON_ONCE(io->cmd != cmd);
+ /* ublk_stop_dev() may have claimed the command already */
+ WARN_ON_ONCE(io->cmd && io->cmd != cmd);
claimed = ublk_claim_cmd(ubq, pdu->tag);
mutex_unlock(&ub->cancel_mutex);
ublk_put_disk(disk);
@@ -2979,20 +2995,75 @@ static inline bool ublk_dev_ready(const struct ublk_device *ub)
return ub->nr_queue_ready == ub->dev_info.nr_hw_queues;
}
-static void ublk_cancel_queue(struct ublk_queue *ubq)
+/*
+ * Claim the fetched commands of a queue and link them for completion
+ * outside of cancel_mutex. A batch queue keeps its commands on the fetch
+ * command list, mark it the way ublk_batch_claim_fcmds() does instead.
+ */
+static struct io_uring_cmd *ublk_claim_queue_cmds(struct ublk_queue *ubq,
+ struct io_uring_cmd *claimed)
+ __must_hold(&ubq->dev->cancel_mutex)
{
- u16 i;
+ u16 tag;
if (ublk_support_batch_io(ubq)) {
- ublk_batch_cancel_queue(ubq);
- return;
+ spin_lock(&ubq->evts_lock);
+ ubq->force_abort = true;
+ spin_unlock(&ubq->evts_lock);
+ return claimed;
}
- for (i = 0; i < ubq->q_depth; i++)
- ublk_cancel_cmd(ubq, i, IO_URING_F_UNLOCKED);
+ for (tag = 0; tag < ubq->q_depth; tag++) {
+ struct io_uring_cmd *cmd = ublk_claim_cmd(ubq, tag);
+
+ if (cmd) {
+ ublk_get_uring_cmd_pdu(cmd)->next_claimed = claimed;
+ claimed = cmd;
+ }
+ }
+ return claimed;
}
-/* Cancel all pending commands, must be called after del_gendisk() returns */
+static void ublk_complete_claimed_cmds(struct io_uring_cmd *cmd)
+{
+ while (cmd) {
+ struct io_uring_cmd *next =
+ ublk_get_uring_cmd_pdu(cmd)->next_claimed;
+
+ io_uring_cmd_done(cmd, UBLK_IO_RES_ABORT, IO_URING_F_UNLOCKED);
+ cmd = next;
+ }
+}
+
+static void ublk_cancel_queue(struct ublk_queue *ubq)
+{
+ struct ublk_device *ub = ubq->dev;
+ struct io_uring_cmd *claimed = NULL;
+ LIST_HEAD(fcmds);
+
+ /*
+ * A queue which is not canceling any more was made ready by a new
+ * server after the marking, leave it alone. Decide that in the
+ * cancel_mutex section which takes the commands, the fetch commands
+ * of a batch queue too, and complete them after it.
+ */
+ mutex_lock(&ub->cancel_mutex);
+ if (ubq->canceling) {
+ claimed = ublk_claim_queue_cmds(ubq, NULL);
+ if (ublk_support_batch_io(ubq))
+ ublk_batch_claim_fcmds(ubq, &fcmds);
+ }
+ mutex_unlock(&ub->cancel_mutex);
+ ublk_complete_claimed_cmds(claimed);
+ if (ublk_support_batch_io(ubq))
+ ublk_batch_complete_fcmds(ubq, &fcmds);
+}
+
+/*
+ * Complete the fetched commands of every queue which is still canceling,
+ * ublk_queue_rq() must not be able to dispatch to them: either
+ * del_gendisk() has returned or ->canceling is visible to it
+ */
static void ublk_cancel_dev(struct ublk_device *ub)
{
u16 i;
@@ -3078,10 +3149,32 @@ static void ublk_stop_dev_unlocked(struct ublk_device *ub)
static void ublk_stop_dev(struct ublk_device *ub)
{
+ struct io_uring_cmd *claimed = NULL;
+ u16 i;
+
mutex_lock(&ub->mutex);
ublk_stop_dev_unlocked(ub);
- mutex_unlock(&ub->mutex);
+ /*
+ * No disk is attached any more, ublk_queue_rq() can't be running and
+ * nothing needs to be quiesced, but START_DEV may take ub->mutex as
+ * soon as it is dropped. Mark the queues and the commands before
+ * that, so that a queue getting ready meantime stays canceling and
+ * such a start sees it.
+ */
+ mutex_lock(&ub->cancel_mutex);
+ ublk_set_canceling(ub, true);
+ for (i = 0; i < ub->dev_info.nr_hw_queues; i++)
+ claimed = ublk_claim_queue_cmds(ublk_get_queue(ub, i), claimed);
+ mutex_unlock(&ub->cancel_mutex);
+ /*
+ * The disk is deleted already, so a partition scan work still
+ * pending or running only finds it gone, and it is fine to wait for
+ * it here. Do that before START_DEV can add a new disk and schedule
+ * a scan for it, which must not be canceled.
+ */
cancel_work_sync(&ub->partition_scan_work);
+ mutex_unlock(&ub->mutex);
+ ublk_complete_claimed_cmds(claimed);
ublk_cancel_dev(ub);
}
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 9/9] ublk: refuse to go live over canceled io commands
2026-09-28 16:00 [PATCH 0/9] ublk: fix dispatch to canceled io commands Josef Bacik
` (7 preceding siblings ...)
2026-09-28 16:00 ` [PATCH 8/9] ublk: claim commands under ub->mutex in ublk_stop_dev() Josef Bacik
@ 2026-09-28 16:00 ` Josef Bacik
2026-09-28 17:35 ` Randy Dunlap
2026-09-29 14:44 ` [PATCH 0/9] ublk: fix dispatch to " Ming Lei
9 siblings, 1 reply; 17+ messages in thread
From: Josef Bacik @ 2026-09-28 16:00 UTC (permalink / raw)
To: Ming Lei, Jens Axboe, Caleb Sander Mateos
Cc: linux-block, linux-kernel, linux-doc, Josef Bacik
START_DEV on a ready device whose commands got canceled adds a disk on
which every request fails. With UBLK_F_USER_RECOVERY, unless
UBLK_F_USER_RECOVERY_FAIL_IO is set, the requests are requeued instead
and never kicked again, so the partition scan read hangs with
disk->open_mutex held until the device is stopped again or deleted.
Every opener blocks behind it. END_USER_RECOVERY on a queue which kept
->canceling marks the device live and leaves its requests parked the
same way.
A command canceled by ublk_claim_cmd() cannot be fetched again before
the queues are reinitialized: FETCH fails with -EBUSY once the device
is ready and with -EINVAL on an active io. Since commit 1133b93fc7f6
("ublk: set canceling flag even when disk is not allocated") and the
previous patches such a device keeps ->canceling set, but nothing stops
it from going live.
In ublk_ctrl_start_dev() check for that and publish ub->ub_disk in one
cancel_mutex section, so that either START_DEV sees the canceled
commands or ublk_start_cancel() sees the disk and quiesces it.
Fix by failing START_DEV and END_USER_RECOVERY with -ENODEV when a
queue is canceling or holds a command canceled after being fetched.
Fixes: 1133b93fc7f6 ("ublk: set canceling flag even when disk is not allocated")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
Documentation/block/ublk.rst | 15 +++++++++--
drivers/block/ublk_drv.c | 63 ++++++++++++++++++++++++++++++++++++++------
2 files changed, 68 insertions(+), 10 deletions(-)
diff --git a/Documentation/block/ublk.rst b/Documentation/block/ublk.rst
index 28300fee22bf..05d702f66a93 100644
--- a/Documentation/block/ublk.rst
+++ b/Documentation/block/ublk.rst
@@ -118,7 +118,13 @@ managing and controlling ublk devices with help of several control commands:
After the server prepares userspace resources (such as creating I/O handler
threads & io_uring for handling ublk IO), this command is sent to the
driver for allocating & exposing ``/dev/ublkb*``. Parameters set via
- ``UBLK_CMD_SET_PARAMS`` are applied for creating the device.
+ ``UBLK_CMD_SET_PARAMS`` are applied for creating the device. The command
+ fails with ``-ENODEV`` if any fetched io command got canceled meantime,
+ by ``UBLK_CMD_STOP_DEV`` or because its io_uring is gone, and the device
+ has to be deleted then. With ``UBLK_F_BATCH_IO`` it fails the same way
+ after a ``UBLK_CMD_STOP_DEV`` sent while no process had ``/dev/ublkc*``
+ open, or after the current one opened it, even if no io command had
+ been fetched yet.
- ``UBLK_CMD_STOP_DEV``
@@ -195,7 +201,12 @@ managing and controlling ublk devices with help of several control commands:
command is accepted after ublk device is quiesced and a new process has
opened ``/dev/ublkc*`` and get all ublk queues be ready. When this command
returns, ublk device is unquiesced and new I/O requests are passed to the
- new process.
+ new process. It fails with ``-ENODEV`` if any of the new io commands got
+ canceled already, and with ``UBLK_F_BATCH_IO`` also if the cancel of a
+ ``UBLK_CMD_QUIESCE_DEV`` reached one of its queues after the old process
+ released ``/dev/ublkc*`` and before that queue got ready. The device has
+ to be deleted then, or the recovery
+ started over after the new process has closed ``/dev/ublkc*``.
- user recovery feature description
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 5e86405b294b..19efa13c205a 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -2757,6 +2757,7 @@ static void ublk_abort_queue(struct ublk_device *ub, struct ublk_queue *ubq)
static struct gendisk *ublk_start_cancel(struct ublk_device *ub)
__must_hold(&ub->cancel_mutex)
{
+ /* sync with ublk_ctrl_start_dev() publishing the disk */
struct gendisk *disk = ublk_get_disk(ub);
if (ub->canceling)
@@ -2772,10 +2773,10 @@ static struct gendisk *ublk_start_cancel(struct ublk_device *ub)
blk_mq_unquiesce_queue(disk->queue);
} else {
/*
- * Disk not yet allocated by ublk_ctrl_start_dev(), so
- * there is no request queue and ublk_queue_rq() cannot
- * be running. Just set the flag; if start_dev proceeds
- * later, new I/O will see canceling and be aborted.
+ * Disk not published by ublk_ctrl_start_dev() or detached
+ * already, so ublk_queue_rq() cannot be running. Just set
+ * the flag, START_DEV fails on canceled commands instead
+ * of adding a disk on top of them.
*/
ublk_set_canceling(ub, true);
}
@@ -4652,11 +4653,35 @@ static bool ublk_validate_user_pid(struct ublk_device *ub, pid_t ublksrv_pid)
return ub->ublksrv_tgid == ublksrv_pid;
}
+/*
+ * The commands canceled by ublk_claim_cmd() can't be fetched again
+ * before the queues are reinitialized, so a ready device must not go live
+ * while a queue is canceling or holds a command canceled after the fetch.
+ */
+static bool ublk_dev_cmds_canceled(struct ublk_device *ub)
+ __must_hold(&ub->cancel_mutex)
+{
+ u16 i;
+
+ for (i = 0; i < ub->dev_info.nr_hw_queues; i++) {
+ struct ublk_queue *ubq = ublk_get_queue(ub, i);
+ bool canceled;
+
+ spin_lock(&ubq->cancel_lock);
+ canceled = ubq->canceling || ublk_queue_has_canceled_io(ubq);
+ spin_unlock(&ubq->cancel_lock);
+ if (canceled)
+ return true;
+ }
+ return false;
+}
+
/*
* Wait until all queues have fetched their I/O commands, and return with
- * ub->mutex held and readiness guaranteed: then every queue's ->canceling
- * is cleared. Ready may regress between wakeup and mutex_lock() (F_BATCH
- * UNPREP, daemon death), so re-check it under the mutex and wait again.
+ * ub->mutex held and readiness guaranteed. Ready may regress between wakeup
+ * and mutex_lock() (F_BATCH UNPREP, daemon death), so re-check it under the
+ * mutex and wait again. The commands may still get canceled after being
+ * fetched, callers check ublk_dev_cmds_canceled().
*/
static int ublk_wait_dev_ready_and_lock(struct ublk_device *ub)
{
@@ -4691,6 +4716,7 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub,
};
struct gendisk *disk;
int ret = -EINVAL;
+ bool canceled;
if (ublksrv_pid <= 0)
return -EINVAL;
@@ -4776,8 +4802,20 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub,
disk->fops = &ub_fops;
disk->private_data = ub;
+ /*
+ * Check and publish under cancel_mutex, so either the canceled
+ * commands are seen here or ublk_start_cancel() sees the disk.
+ */
+ mutex_lock(&ub->cancel_mutex);
+ canceled = ublk_dev_cmds_canceled(ub);
+ if (!canceled)
+ ub->ub_disk = disk;
+ mutex_unlock(&ub->cancel_mutex);
+ if (canceled) {
+ ret = -ENODEV;
+ goto out_put_disk;
+ }
ub->dev_info.ublksrv_pid = ub->ublksrv_tgid;
- ub->ub_disk = disk;
ublk_apply_params(ub);
@@ -4824,6 +4862,7 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub,
ublk_detach_disk(ub);
ublk_put_device(ub);
}
+out_put_disk:
if (ret)
put_disk(disk);
out_unlock:
@@ -5350,6 +5389,7 @@ static int ublk_ctrl_end_recovery(struct ublk_device *ub,
{
int ublksrv_pid = (int)header->data[0];
int ret = -EINVAL;
+ bool canceled;
pr_devel("%s: Waiting for all FETCH_REQs, dev id %d...\n", __func__,
header->dev_id);
@@ -5372,6 +5412,13 @@ static int ublk_ctrl_end_recovery(struct ublk_device *ub,
ret = -EBUSY;
goto out_unlock;
}
+ mutex_lock(&ub->cancel_mutex);
+ canceled = ublk_dev_cmds_canceled(ub);
+ mutex_unlock(&ub->cancel_mutex);
+ if (canceled) {
+ ret = -ENODEV;
+ goto out_unlock;
+ }
ub->dev_info.ublksrv_pid = ub->ublksrv_tgid;
ub->dev_info.state = UBLK_S_DEV_LIVE;
pr_devel("%s: new ublksrv_pid %d, dev id %d\n",
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 9/9] ublk: refuse to go live over canceled io commands
2026-09-28 16:00 ` [PATCH 9/9] ublk: refuse to go live over canceled io commands Josef Bacik
@ 2026-09-28 17:35 ` Randy Dunlap
0 siblings, 0 replies; 17+ messages in thread
From: Randy Dunlap @ 2026-09-28 17:35 UTC (permalink / raw)
To: Josef Bacik, Ming Lei, Jens Axboe, Caleb Sander Mateos
Cc: linux-block, linux-kernel, linux-doc
Hi,
On 9/28/26 9:00 AM, Josef Bacik wrote:
> diff --git a/Documentation/block/ublk.rst b/Documentation/block/ublk.rst
> index 28300fee22bf..05d702f66a93 100644
> --- a/Documentation/block/ublk.rst
> +++ b/Documentation/block/ublk.rst
> @@ -118,7 +118,13 @@ managing and controlling ublk devices with help of several control commands:
> After the server prepares userspace resources (such as creating I/O handler
> threads & io_uring for handling ublk IO), this command is sent to the
> driver for allocating & exposing ``/dev/ublkb*``. Parameters set via
> - ``UBLK_CMD_SET_PARAMS`` are applied for creating the device.
> + ``UBLK_CMD_SET_PARAMS`` are applied for creating the device. The command
> + fails with ``-ENODEV`` if any fetched io command got canceled meantime,
IO or I/O
command was canceled
> + by ``UBLK_CMD_STOP_DEV`` or because its io_uring is gone, and the device
> + has to be deleted then. With ``UBLK_F_BATCH_IO`` it fails the same way
> + after a ``UBLK_CMD_STOP_DEV`` sent while no process had ``/dev/ublkc*``
> + open, or after the current one opened it, even if no io command had
IO or I/O
> + been fetched yet.
>
> - ``UBLK_CMD_STOP_DEV``
>
> @@ -195,7 +201,12 @@ managing and controlling ublk devices with help of several control commands:
> command is accepted after ublk device is quiesced and a new process has
> opened ``/dev/ublkc*`` and get all ublk queues be ready. When this command
> returns, ublk device is unquiesced and new I/O requests are passed to the
> - new process.
> + new process. It fails with ``-ENODEV`` if any of the new io commands got
IO or I/O
commands was
> + canceled already, and with ``UBLK_F_BATCH_IO`` also if the cancel of a
> + ``UBLK_CMD_QUIESCE_DEV`` reached one of its queues after the old process
> + released ``/dev/ublkc*`` and before that queue got ready. The device has
was ready.
> + to be deleted then, or the recovery
> + started over after the new process has closed ``/dev/ublkc*``.
>
> - user recovery feature description
>
--
~Randy
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 0/9] ublk: fix dispatch to canceled io commands
2026-09-28 16:00 [PATCH 0/9] ublk: fix dispatch to canceled io commands Josef Bacik
` (8 preceding siblings ...)
2026-09-28 16:00 ` [PATCH 9/9] ublk: refuse to go live over canceled io commands Josef Bacik
@ 2026-09-29 14:44 ` Ming Lei
2026-09-30 14:17 ` Josef Bacik
9 siblings, 1 reply; 17+ messages in thread
From: Ming Lei @ 2026-09-29 14:44 UTC (permalink / raw)
To: Josef Bacik
Cc: Jens Axboe, Caleb Sander Mateos, linux-block, linux-kernel, linux-doc
Hi Josef,
On Mon, Sep 28, 2026 at 04:00:36PM +0000, Josef Bacik wrote:
> ublk can dispatch a block request to an io command which is completed
> already, and the kernel oopses in ublk_queue_rq() on a NULL io->cmd.
> Before commit f7700a4415af ("ublk: fix use-after-free in
> ublk_cancel_cmd()") it is a freed io_uring request instead. Commit
> 1133b93fc7f6 ("ublk: set canceling flag even when disk is not
> allocated") fixed the io_uring exit route before the first start.
> These are the routes next to it:
>
> 1. STOP_DEV on a device which is ready but not started, then
> START_DEV. ublk_stop_dev() cancels the fetched commands after
> dropping ub->mutex, without marking the queues as canceling.
It looks two races: STOP_DEV vs. START_DEV, STOP_DEV vs. FETCH.
Looks fast io path shouldn't be touched for fixing the races.
> 2. A partial FETCH round whose task exits, once another task
> completes the round.
> 3. During recovery, the task of a queue which is ready already
> exiting before the last queue is ready.
2 and 3 could be solved in single simpler patch by making use of the
ub->canceling flag, and it is easier for backport.
From 83d95ff6f06ad71705e2432ffc58971bec0ec648 Mon Sep 17 00:00:00 2001
From: Ming Lei <tom.leiming@gmail.com>
Date: Tue, 29 Sep 2026 08:25:05 -0500
Subject: [PATCH] ublk: keep a canceled FETCH round canceling until the server
is gone
A cancel completes a fetched io command and sets io->cmd to NULL, but
the io still counts as ready. Only ubq->canceling stops ublk_queue_rq()
from using the NULL io->cmd. Two paths clear that flag too early:
partial FETCH round recovery, two queues
------------------- --------------------
task A: FETCH tags 0-2 q0 ready: q0->canceling = false
task A exits: q0 task exits:
start_cancel(): mark queues start_cancel(): ub->canceling
tags 0-2: io->cmd = NULL is still set -> q0 not marked
task B: FETCH tag 3 q0: io->cmd = NULL
queue ready: canceling = false q1 ready
START_DEV END_USER_RECOVERY
read -> ublk_queue_cmd(NULL) read on q0 -> ublk_queue_cmd(NULL)
A FETCH round runs from the open of /dev/ublkcN until
ublk_reset_ch_dev() resets the queues. A canceled command can't be
fetched again in the same round. So the ready check only needs one
fact: did this round see a cancel? ub->canceling already records it:
ublk_start_cancel() sets it under cancel_mutex before any command is
taken. It is only cleared too early, when all queues are ready.
Fix:
- clear ub->canceling only in ublk_reset_ch_dev()
- when a queue gets ready, clear ubq->canceling only if ub->canceling
is not set, and check it under cancel_mutex
The check and the marking are ordered by cancel_mutex:
check first: queue cleared -> start_cancel() marks it again
-> command taken
cancel first: check sees ub->canceling -> queue stays canceling
While ub->canceling is set, no queue clears its flag. So the "all
queues marked" shortcut in ublk_start_cancel() is correct again.
Fixes: 728cbac5fe21 ("ublk: move device reset into ublk_ch_release()")
Fixes: 3f3850785594 ("ublk: fix batch I/O recovery -ENODEV error")
Cc: stable@vger.kernel.org # v6.17+: needs cancel_mutex
Reported-by: Josef Bacik <josef@toxicpanda.com>
Closes: https://lore.kernel.org/linux-block/20260928-b4-ublk-cancel-stop-v1-0-4a4360232a46@toxicpanda.com/
Signed-off-by: Ming Lei <tom.leiming@gmail.com>
Assisted-by: LLM
---
drivers/block/ublk_drv.c | 54 ++++++++++++++++++++++++----------------
1 file changed, 33 insertions(+), 21 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 66eb55e7162e..38ed7d0e3979 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -334,6 +334,11 @@ struct ublk_device {
u16 nr_queue_ready;
bool unprivileged_daemons;
struct mutex cancel_mutex;
+ /*
+ * A cancel started in this FETCH round. Set by ublk_set_canceling(),
+ * cleared only by ublk_reset_ch_dev() when a new round starts. While
+ * it is set, no queue clears its ->canceling.
+ */
bool canceling;
pid_t ublksrv_tgid;
struct delayed_work exit_work;
@@ -2415,6 +2420,11 @@ static void ublk_reset_ch_dev(struct ublk_device *ub)
spin_unlock(&ubq->cancel_lock);
}
+ /* a new FETCH round starts, the queues stay canceling until ready */
+ mutex_lock(&ub->cancel_mutex);
+ ub->canceling = false;
+ mutex_unlock(&ub->cancel_mutex);
+
/* set to NULL, otherwise new tasks cannot mmap io_cmd_buf */
ub->mm = NULL;
ub->nr_queue_ready = 0;
@@ -3024,11 +3034,19 @@ static void ublk_reset_io_flags(struct ublk_queue *ubq, struct ublk_io *io)
}
/* reset per-queue io flags */
-static void ublk_queue_reset_io_flags(struct ublk_queue *ubq)
+static void ublk_queue_reset_io_flags(struct ublk_device *ub,
+ struct ublk_queue *ubq)
{
- spin_lock(&ubq->cancel_lock);
- ubq->canceling = false;
- spin_unlock(&ubq->cancel_lock);
+ /*
+ * A cancel in this FETCH round took a command which still counts as
+ * ready, so the queue has to stay canceling. ub->canceling is set
+ * under cancel_mutex before any command is taken: either we see it
+ * here, or the cancel marks this queue again later.
+ */
+ mutex_lock(&ub->cancel_mutex);
+ if (!ub->canceling)
+ ubq->canceling = false;
+ mutex_unlock(&ub->cancel_mutex);
ubq->fail_io = false;
ubq->force_abort = false;
}
@@ -3051,24 +3069,17 @@ static void ublk_mark_io_ready(struct ublk_device *ub, u16 q_id,
ub->nr_queue_ready++;
/*
- * Reset queue flags as soon as this queue is ready.
- * This clears the canceling flag, allowing batch FETCH commands
- * to succeed during recovery without waiting for all queues.
+ * Reset queue flags as soon as this queue is ready. Unless
+ * this round saw a cancel, this clears the canceling flag,
+ * allowing batch FETCH commands to succeed during recovery
+ * without waiting for all queues.
*/
- ublk_queue_reset_io_flags(ubq);
+ ublk_queue_reset_io_flags(ub, ubq);
}
- /* Check if all queues are ready */
- if (ublk_dev_ready(ub)) {
- /*
- * All queues ready - clear device-level canceling flag
- * and wake ublk_dev_ready() waiters.
- */
- mutex_lock(&ub->cancel_mutex);
- ub->canceling = false;
- mutex_unlock(&ub->cancel_mutex);
+ /* All queues ready - wake ublk_dev_ready() waiters */
+ if (ublk_dev_ready(ub))
wake_up_var(&ub->nr_queue_ready);
- }
}
static inline int ublk_check_cmd_op(u32 cmd_op)
@@ -4433,9 +4444,10 @@ static bool ublk_validate_user_pid(struct ublk_device *ub, pid_t ublksrv_pid)
/*
* Wait until all queues have fetched their I/O commands, and return with
- * ub->mutex held and readiness guaranteed: then every queue's ->canceling
- * is cleared. Ready may regress between wakeup and mutex_lock() (F_BATCH
- * UNPREP, daemon death), so re-check it under the mutex and wait again.
+ * ub->mutex held and readiness guaranteed. The queues stay canceling if
+ * this round saw a cancel, see ublk_queue_reset_io_flags(). Ready may
+ * regress between wakeup and mutex_lock() (F_BATCH UNPREP, daemon death),
+ * so re-check it under the mutex and wait again.
*/
static int ublk_wait_dev_ready_and_lock(struct ublk_device *ub)
{
--
2.55.0
Thanks,
Ming
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 0/9] ublk: fix dispatch to canceled io commands
2026-09-29 14:44 ` [PATCH 0/9] ublk: fix dispatch to " Ming Lei
@ 2026-09-30 14:17 ` Josef Bacik
0 siblings, 0 replies; 17+ messages in thread
From: Josef Bacik @ 2026-09-30 14:17 UTC (permalink / raw)
To: Ming Lei
Cc: Jens Axboe, Caleb Sander Mateos, linux-block, linux-kernel, linux-doc
On Tue, Sep 29, 2026 at 09:44:16AM -0500, Ming Lei wrote:
> It looks two races: STOP_DEV vs. START_DEV, STOP_DEV vs. FETCH.
>
> Looks fast io path shouldn't be touched for fixing the races.
>
> > 2. A partial FETCH round whose task exits, once another task
> > completes the round.
> > 3. During recovery, the task of a queue which is ready already
> > exiting before the last queue is ready.
>
> 2 and 3 could be solved in single simpler patch by making use of the
> ub->canceling flag, and it is easier for backport.
Agreed, yours is much simpler, and keeping the flag set for the whole
FETCH round is the right model. I ran it on top of for-next (d70609a2f68c)
with KASAN and lockdep through my reproducers and the ublk selftests. The
oopses for 2 and 3 are gone, and recover_01-04, batch_01-03, generic_17,
stress_01/02/05 and 60 batch QUIESCE_DEV/recover cycles pass.
What's left for 2 and 3 is that the device still comes up. For 2,
START_DEV returns 0 and the new disk fails every request. For 3,
END_USER_RECOVERY returns 0, the device is LIVE, and every read on the
queue whose task exited sits requeued forever, since the queue stays
canceling and nothing kicks the requeue list. With ub->canceling
covering the whole round that's a small check: return -ENODEV from
START_DEV and END_USER_RECOVERY when ub->canceling is set, checked under
cancel_mutex against publishing ub->ub_disk. The server can't fetch
those commands again anyway. Patch 9 of my series did that on the old
model, I'll redo it on top of yours.
For 1, your patch alone still oopses in ublk_queue_rq() from the
partition scan when START_DEV follows STOP_DEV, same as before. I'll
respin my series as just that, on top of your patch and without touching
the commit path: STOP_DEV marks the queues canceling and takes the
fetched commands under ub->mutex, and FETCH marks its command cancelable
before it publishes it, so a cancel from the control path never
completes a command io_uring doesn't have on its cancelable list yet.
For your patch:
Tested-by: Josef Bacik <josef@toxicpanda.com>
Thanks,
Josef
^ permalink raw reply [flat|nested] 17+ messages in thread