From: Ming Lei <tom.leiming@gmail.com>
To: linux-block@vger.kernel.org
Cc: Ming Lei <tom.leiming@gmail.com>, Jens Axboe <axboe@kernel.dk>,
Caleb Sander Mateos <csander@purestorage.com>,
Josef Bacik <josef@toxicpanda.com>
Subject: [PATCH 6/8] ublk: let STOP_DEV cancel the server's commands before its release
Date: Thu, 1 Oct 2026 07:54:20 -0500 [thread overview]
Message-ID: <20261001125422.1364260-7-tom.leiming@gmail.com> (raw)
In-Reply-To: <20261001125422.1364260-1-tom.leiming@gmail.com>
STOP_DEV cancels the fetched commands after dropping ub->mutex, so
START_DEV and FETCH can run in between:
STOP_DEV START_DEV / FETCH
lock ub->mutex
ublk_stop_dev_unlocked() (not started: does nothing)
unlock ub->mutex
START_DEV goes live, schedules scan
FETCH publishes a new command
cancel_work_sync(scan) cancels the new disk's scan
ublk_cancel_dev() takes the new command: NULL io->cmd
on a queue which isn't canceling
A START_DEV after STOP_DEV also goes live over the canceled commands.
Hold a reference on the server's /dev/ublkcN while canceling:
STOP_DEV
lock ub->mutex
ublk_stop_dev_unlocked()
cancel_work_sync(scan)
server attached: take a reference on its file, set STOPPING
unlock ub->mutex
ublk_cancel_dev()
drop the reference
- STOPPING makes FETCH and PREP get UBLK_IO_RES_ABORT and START_DEV
-EBUSY; a START_DEV or END_USER_RECOVERY waiting for the device
to get ready wakes up too; the reset in the server's release
clears it
- the release can't run before the reference is dropped, so no new
server can attach, and the cancel only meets this server's commands
- the cancel still runs after the unlock: io_uring_cmd_done() may take
uring_lock, under which FETCH takes ub->mutex
ublk_ch_open() saves the file in ->ch_file and ublk_ch_release() clears
it, under cancel_mutex. The release runs after the last reference is
gone, so take ours with file_ref_get(). Drop it with __fput_sync(): if
it is the last one, fput() would defer the release to this task, which
DEL_DEV then makes wait for the device to be freed.
A server which opened the device but hasn't fetched is stopped too. The
cancel callback's io->cmd check allows NULL: the cancel may take the
command meanwhile.
Fixes: 85248d670b71 ("ublk: move ublk_cancel_dev() out of ub->mutex")
Cc: stable@vger.kernel.org
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>
---
drivers/block/ublk_drv.c | 85 +++++++++++++++++++++++++++++++++++-----
1 file changed, 76 insertions(+), 9 deletions(-)
diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
index 162a4d1a2e2a..f57d544c1da2 100644
--- a/drivers/block/ublk_drv.c
+++ b/drivers/block/ublk_drv.c
@@ -321,6 +321,8 @@ struct ublk_device {
#define UB_STATE_OPEN 0
#define UB_STATE_USED 1
#define UB_STATE_DELETED 2
+/* STOP_DEV canceled the server's commands, until its release */
+#define UB_STATE_STOPPING 3
unsigned long state;
int ub_number;
@@ -334,6 +336,8 @@ struct ublk_device {
u16 nr_queue_ready;
bool unprivileged_daemons;
struct mutex cancel_mutex;
+ /* the open /dev/ublkcN, protected by cancel_mutex */
+ struct file *ch_file;
/*
* 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
@@ -2406,6 +2410,9 @@ static int ublk_ch_open(struct inode *inode, struct file *filp)
return -EBUSY;
filp->private_data = ub;
ub->ublksrv_tgid = current->tgid;
+ mutex_lock(&ub->cancel_mutex);
+ ub->ch_file = filp;
+ mutex_unlock(&ub->cancel_mutex);
return 0;
}
@@ -2432,6 +2439,7 @@ static void ublk_reset_ch_dev(struct ublk_device *ub)
ub->nr_queue_ready = 0;
ub->unprivileged_daemons = false;
ub->ublksrv_tgid = -1;
+ clear_bit(UB_STATE_STOPPING, &ub->state);
}
static struct gendisk *ublk_get_disk(struct ublk_device *ub)
@@ -2635,6 +2643,9 @@ static int ublk_ch_release(struct inode *inode, struct file *filp)
{
struct ublk_device *ub = filp->private_data;
+ mutex_lock(&ub->cancel_mutex);
+ ub->ch_file = NULL;
+ mutex_unlock(&ub->cancel_mutex);
/*
* Grab ublk device reference, so it won't be gone until we are
* really released from work function.
@@ -2896,6 +2907,7 @@ 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 *cur;
struct task_struct *task;
struct ublk_io *io;
@@ -2912,7 +2924,9 @@ static void ublk_uring_cmd_cancel_fn(struct io_uring_cmd *cmd,
ublk_start_cancel(ubq->dev);
- WARN_ON_ONCE(io->cmd != cmd);
+ /* NULL if STOP_DEV's cancel took it meanwhile */
+ cur = READ_ONCE(io->cmd);
+ WARN_ON_ONCE(cur && cur != cmd);
ublk_cancel_cmd(ubq, pdu->tag, issue_flags);
}
@@ -3023,13 +3037,54 @@ static void ublk_stop_dev_unlocked(struct ublk_device *ub)
put_disk(disk);
}
+static struct file *ublk_get_ch_file(struct ublk_device *ub)
+{
+ struct file *file;
+
+ mutex_lock(&ub->cancel_mutex);
+ file = ub->ch_file;
+ if (file && !file_ref_get(&file->f_ref))
+ file = NULL;
+ mutex_unlock(&ub->cancel_mutex);
+ return file;
+}
+
static void ublk_stop_dev(struct ublk_device *ub)
{
+ struct file *file;
+
+ /*
+ * FETCH, PREP and START_DEV take ub->mutex. If a server has
+ * /dev/ublkcN open, set STOPPING, which turns it away, and hold a
+ * reference on the file: the server's release, whose reset clears
+ * STOPPING and lets a new server attach, can't run before we drop it.
+ * So the cancel can run after the unlock and only meets this server's
+ * commands; it has to: io_uring_cmd_done() may take uring_lock, under
+ * which FETCH takes ub->mutex.
+ */
mutex_lock(&ub->mutex);
ublk_stop_dev_unlocked(ub);
- mutex_unlock(&ub->mutex);
cancel_work_sync(&ub->partition_scan_work);
+ file = ublk_get_ch_file(ub);
+ if (file) {
+ /*
+ * The server has to close /dev/ublkcN before this device can
+ * be started again: the reset in its release clears STOPPING.
+ */
+ set_bit(UB_STATE_STOPPING, &ub->state);
+ /* for wake_up_var() below, see wake_up_bit() */
+ smp_mb__after_atomic();
+ }
+ mutex_unlock(&ub->mutex);
+
+ /* no server: nothing to cancel */
+ if (!file)
+ return;
+
+ /* wake a START_DEV waiting for the device to get ready */
+ wake_up_var(&ub->nr_queue_ready);
ublk_cancel_dev(ub);
+ __fput_sync(file);
}
static void ublk_reset_io_flags(struct ublk_queue *ubq, struct ublk_io *io)
@@ -3295,6 +3350,9 @@ 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)
{
+ if (test_bit(UB_STATE_STOPPING, &ub->state))
+ return UBLK_IO_RES_ABORT;
+
/* UBLK_IO_FETCH_REQ is only allowed before dev is setup */
if (ublk_dev_ready(ub))
return -EBUSY;
@@ -4473,22 +4531,27 @@ static bool ublk_validate_user_pid(struct ublk_device *ub, pid_t ublksrv_pid)
return ub->ublksrv_tgid == ublksrv_pid;
}
+static bool ublk_dev_ready_or_stopping(const struct ublk_device *ub)
+{
+ return ublk_dev_ready(ub) || test_bit(UB_STATE_STOPPING, &ub->state);
+}
+
/*
- * Wait until all queues have fetched their I/O commands, and return with
- * 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.
+ * Wait until all queues have fetched their I/O commands, or STOP_DEV set
+ * UB_STATE_STOPPING, and return with ub->mutex held. 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)
{
while (true) {
if (wait_var_event_interruptible(&ub->nr_queue_ready,
- ublk_dev_ready(ub)))
+ ublk_dev_ready_or_stopping(ub)))
return -EINTR;
mutex_lock(&ub->mutex);
- if (ublk_dev_ready(ub))
+ if (ublk_dev_ready_or_stopping(ub))
return 0;
mutex_unlock(&ub->mutex);
}
@@ -4588,6 +4651,10 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub,
ret = -EEXIST;
goto out_unlock;
}
+ if (test_bit(UB_STATE_STOPPING, &ub->state)) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
disk = blk_mq_alloc_disk(&ub->tag_set, &lim, NULL);
if (IS_ERR(disk)) {
--
2.55.0
next prev parent reply other threads:[~2026-10-01 12:54 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-01 12:54 [PATCH 0/8] ublk: don't dispatch to canceled io commands Ming Lei
2026-10-01 12:54 ` [PATCH 1/8] ublk: keep a canceled FETCH round canceling until the server is gone Ming Lei
2026-10-01 12:54 ` [PATCH 2/8] ublk: mark the io command cancelable before publishing it Ming Lei
2026-10-01 12:54 ` [PATCH 3/8] ublk: mark the batch fetch command cancelable before linking it Ming Lei
2026-10-01 12:54 ` [PATCH 4/8] ublk: reset the FETCH round in release also without a disk Ming Lei
2026-10-01 12:54 ` [PATCH 5/8] ublk: reset the FETCH round under ub->mutex Ming Lei
2026-10-01 12:54 ` Ming Lei [this message]
2026-10-01 12:54 ` [PATCH 7/8] selftests: ublk: move the control command helpers into ctrl.c Ming Lei
2026-10-01 12:54 ` [PATCH 8/8] selftests: ublk: add test for going live over canceled io commands Ming Lei
2026-10-05 16:23 ` [PATCH] ublk: refuse to go live after an io command was canceled Josef Bacik
2026-10-06 14:14 ` Ming Lei
2026-10-05 16:23 ` [PATCH v2] " Josef Bacik
2026-10-05 18:50 ` [PATCH 0/8] ublk: don't dispatch to canceled io commands Josef Bacik
2026-10-06 16:10 ` [PATCH 0/4] ublk: fix UBLK_CMD_QUIESCE_DEV leaving commands behind Josef Bacik
2026-10-06 13:05 ` [PATCH 1/4] ublk: don't cancel commands in QUIESCE_DEV on a device that isn't live Josef Bacik
2026-10-06 14:49 ` [PATCH 2/4] ublk: drop QUIESCE_DEV's wait for an idle command Josef Bacik
2026-10-06 14:50 ` [PATCH 3/4] ublk: give the command back from COMMIT_AND_FETCH on a canceling queue Josef Bacik
2026-10-06 14:50 ` [PATCH 4/4] ublk: keep canceling in QUIESCE_DEV until the server's commands are taken Josef Bacik
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261001125422.1364260-7-tom.leiming@gmail.com \
--to=tom.leiming@gmail.com \
--cc=axboe@kernel.dk \
--cc=csander@purestorage.com \
--cc=josef@toxicpanda.com \
--cc=linux-block@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.