From: Mohamed Khalfella <mkhalfella@purestorage.com>
To: linux-block@vger.kernel.org
Cc: shinichiro.kawasaki@wdc.com, Keith Busch <kbusch@kernel.org>,
Jens Axboe <axboe@kernel.dk>, Christoph Hellwig <hch@lst.de>,
Sagi Grimberg <sagi@grimberg.me>, Hannes Reinecke <hare@suse.de>,
John Meneghini <jmeneghi@redhat.com>,
Jesse Taube <jtaubepe@redhat.com>,
Randy Jennings <randyj@purestorage.com>,
Dhaval Giani <dgiani@purestorage.com>,
Mohamed Khalfella <mkhalfella@purestorage.com>
Subject: [PATCH blktests 2/5] src/miniublk: add IO delay injection
Date: Wed, 16 Sep 2026 20:06:22 -0600 [thread overview]
Message-ID: <20260917020752.1672578-3-mkhalfella@purestorage.com> (raw)
In-Reply-To: <20260917020752.1672578-1-mkhalfella@purestorage.com>
Tests that exercise error handling in a block driver stacked on ublk need
a way to hold an IO back without failing it. Add an INJECT_DELAY control
command which delays a given number of reads or writes before they are
issued to the target.
The delay is an io_uring timeout on the same ring rather than a sleep in
the queue thread, marked with UBLK_DELAY_MARK in tgt_data to tell its
completion apart from a real IO. It is counted in io_inflight to be
waited for.
Signed-off-by: Mohamed Khalfella <mkhalfella@purestorage.com>
---
src/miniublk.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 109 insertions(+), 2 deletions(-)
diff --git a/src/miniublk.c b/src/miniublk.c
index 30bd7f5..8dc2a56 100644
--- a/src/miniublk.c
+++ b/src/miniublk.c
@@ -19,6 +19,7 @@
#include <limits.h>
#include <string.h>
#include <errno.h>
+#include <stdatomic.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
@@ -49,7 +50,8 @@
#define UBLK_CTRL_MSG_MAGIC 0x6b6c6275
enum {
- UBLK_CTRL_CMD_PING = 0,
+ UBLK_CTRL_CMD_PING = 0,
+ UBLK_CTRL_CMD_INJECT_DELAY = 1,
};
struct ublk_ctrl_msg {
@@ -84,6 +86,8 @@ struct ublk_io {
unsigned int flags;
unsigned int result;
+
+ struct __kernel_timespec delay_ts;
};
struct ublk_tgt_ops {
@@ -136,6 +140,10 @@ struct ublk_dev {
/* daemon control channel, 0 means it is disabled */
int ctrl_port;
int ctrl_sock;
+
+ atomic_int inject_op;
+ atomic_int inject_secs;
+ atomic_int inject_count;
};
#ifndef offsetof
@@ -190,6 +198,13 @@ static inline unsigned int user_data_to_op(__u64 user_data)
return (user_data >> 16) & 0xff;
}
+#define UBLK_DELAY_MARK 1
+
+static inline unsigned int user_data_to_tgt_data(__u64 user_data)
+{
+ return (user_data >> 24) & 0xff;
+}
+
static void ublk_err(const char *fmt, ...)
{
va_list ap;
@@ -509,6 +524,7 @@ static struct ublk_dev *ublk_ctrl_init()
dev->use_ioctl = true; /* use ioctl opcodes by default */
dev->ctrl_sock = -1;
+ dev->inject_op = -1;
dev->ctrl_fd = open(CTRL_DEV, O_RDWR);
if (dev->ctrl_fd < 0) {
@@ -637,6 +653,26 @@ static int ublk_ctrl_msg_handle(struct ublk_dev *dev,
case UBLK_CTRL_CMD_PING:
rsp->data[0] = dev->dev_info.dev_id;
return 0;
+ case UBLK_CTRL_CMD_INJECT_DELAY: {
+ int op = req->data[0];
+ int secs = req->data[1];
+ int count = req->data[2];
+
+ if (op != UBLK_IO_OP_READ && op != UBLK_IO_OP_WRITE)
+ return -EINVAL;
+ if (secs <= 0 || count <= 0)
+ return -EINVAL;
+
+ dev->inject_op = op;
+ dev->inject_secs = secs;
+ /* arm the count last, the request becomes visible at once */
+ dev->inject_count = count;
+
+ ublk_log("dev %d: delay next %d %s io(s) by %d secs\n",
+ dev->dev_info.dev_id, count,
+ op == UBLK_IO_OP_READ ? "read" : "write", secs);
+ return 0;
+ }
default:
ublk_dbg(UBLK_DBG_DEV, "%s: unknown control command %u\n",
__func__, req->cmd);
@@ -773,6 +809,66 @@ static void ublk_dev_unprep(struct ublk_dev *dev)
close(dev->fds[0]);
}
+static int ublk_inject_claim(struct ublk_queue *q, unsigned int ublk_op)
+{
+ struct ublk_dev *dev = q->dev;
+ int secs;
+
+ if (dev->inject_count <= 0)
+ return 0;
+
+ if (dev->inject_op != (int)ublk_op)
+ return 0;
+
+ secs = dev->inject_secs;
+ if (secs <= 0)
+ return 0;
+
+ if (atomic_fetch_sub(&dev->inject_count, 1) <= 0) {
+ /* another queue took the last one */
+ atomic_fetch_add(&dev->inject_count, 1);
+ return 0;
+ }
+
+ return secs;
+}
+
+static int ublk_delay_io(struct ublk_queue *q, int tag)
+{
+ const struct ublksrv_io_desc *iod = ublk_get_iod(q, tag);
+ unsigned int ublk_op = ublksrv_get_op(iod);
+ struct ublk_io *io = &q->ios[tag];
+ struct io_uring_sqe *sqe;
+ int secs;
+
+ secs = ublk_inject_claim(q, ublk_op);
+ if (!secs)
+ return 0;
+
+ sqe = io_uring_get_sqe(&q->ring);
+ if (!sqe) {
+ ublk_err("%s: run out of sqe %d, tag %d\n", __func__,
+ q->q_id, tag);
+ atomic_fetch_add(&q->dev->inject_count, 1);
+ return 0;
+ }
+
+ io->delay_ts.tv_sec = secs;
+ io->delay_ts.tv_nsec = 0;
+
+ io_uring_prep_timeout(sqe, &io->delay_ts, 0, 0);
+ /* bit63 marks us as tgt io, tgt_data marks us as the delay timeout */
+ sqe->user_data = build_user_data(tag, ublk_op, UBLK_DELAY_MARK, 1);
+
+ q->io_inflight++;
+
+ ublk_dbg(UBLK_DBG_IO, "%s: dev %d q %d tag %d: delay %s io by %d secs\n",
+ __func__, q->dev->dev_info.dev_id, q->q_id, tag,
+ ublk_op == UBLK_IO_OP_READ ? "read" : "write", secs);
+
+ return 1;
+}
+
static int ublk_queue_io_cmd(struct ublk_queue *q,
struct ublk_io *io, unsigned tag)
{
@@ -896,6 +992,16 @@ static inline void ublksrv_handle_tgt_cqe(struct ublk_queue *q,
{
unsigned tag = user_data_to_tag(cqe->user_data);
+ /*
+ * A delay timeout completes with -ETIME. It is not a real io, so hand
+ * the tag back to queue_io() instead of completing it.
+ */
+ if (user_data_to_tgt_data(cqe->user_data) == UBLK_DELAY_MARK) {
+ q->io_inflight--;
+ q->tgt_ops->queue_io(q, tag);
+ return;
+ }
+
if (cqe->res < 0 && cqe->res != -EAGAIN)
ublk_err("%s: failed tgt io: res %d qid %u tag %u, cmd_op %u\n",
__func__, cqe->res, q->q_id,
@@ -937,7 +1043,8 @@ static void ublk_handle_cqe(struct io_uring *r,
if (cqe->res == UBLK_IO_RES_OK) {
ublk_assert(tag < q->q_depth);
- q->tgt_ops->queue_io(q, tag);
+ if (!ublk_delay_io(q, tag))
+ q->tgt_ops->queue_io(q, tag);
} else {
/*
* COMMIT_REQ will be completed immediately since no fetching
--
2.55.0
next prev parent reply other threads:[~2026-09-17 2:09 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-17 2:06 [PATCH blktests 0/5] nvme: detect ABA ghost writes on multipath fabrics Mohamed Khalfella
2026-09-17 2:06 ` [PATCH blktests 1/5] src/miniublk: add a control channel to the daemon Mohamed Khalfella
2026-09-17 2:06 ` Mohamed Khalfella [this message]
2026-09-17 2:06 ` [PATCH blktests 3/5] src/miniublk: add the inject command Mohamed Khalfella
2026-09-17 2:06 ` [PATCH blktests 4/5] src/nvme-ghost-write-detector: add an ABA ghost write detector Mohamed Khalfella
2026-09-21 18:38 ` Jesse Taube
2026-09-23 17:00 ` Mohamed Khalfella
2026-09-17 2:06 ` [PATCH blktests 5/5] nvme/070: test for ABA ghost writes on a multipath fabrics namespace Mohamed Khalfella
2026-09-22 17:07 ` Jesse Taube
2026-09-23 16:56 ` Mohamed Khalfella
2026-09-23 8:18 ` [PATCH blktests 0/5] nvme: detect ABA ghost writes on multipath fabrics Shin'ichiro Kawasaki
2026-09-23 17:02 ` Mohamed Khalfella
2026-10-02 3:03 ` Shin'ichiro Kawasaki
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=20260917020752.1672578-3-mkhalfella@purestorage.com \
--to=mkhalfella@purestorage.com \
--cc=axboe@kernel.dk \
--cc=dgiani@purestorage.com \
--cc=hare@suse.de \
--cc=hch@lst.de \
--cc=jmeneghi@redhat.com \
--cc=jtaubepe@redhat.com \
--cc=kbusch@kernel.org \
--cc=linux-block@vger.kernel.org \
--cc=randyj@purestorage.com \
--cc=sagi@grimberg.me \
--cc=shinichiro.kawasaki@wdc.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox