Linux block layer
 help / color / mirror / Atom feed
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


  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