From: "Denis V. Lunev" <den@openvz.org>
To: andrey.drobyshev@virtuozzo.com
Cc: svt-core@virtuozzo.com, den@openvz.org
Subject: [PATCH hci-8.1 2/5] scsi-disk: fix UNMAP racing with inactivation of block nodes #VSTOR-151532
Date: Mon, 26 Oct 2026 18:49:26 +0100 [thread overview]
Message-ID: <20261026174929.685720-3-den@openvz.org> (raw)
In-Reply-To: <20261026174929.685720-1-den@openvz.org>
From: Denis V. Lunev <den@openvz.org>
scsi-disk turns a multi-descriptor UNMAP into one discard per
descriptor and submits the next one from the completion callback of
the previous. Nothing holds the BlockBackend in-flight counter between
two descriptors, so a drain only waits for the discard currently in
flight. When that discard completes inside drained_begin, the callback
submits the next one, blk_wait_while_drained() queues it, and the drain
returns with the UNMAP half done.
bdrv_inactivate_all() drains, inactivates the nodes and ends the drain,
which resumes the queued discard on an inactive node:
bdrv_co_write_req_prepare: Assertion
`!(bs->open_flags & BDRV_O_INACTIVE)' failed.
A guest that issues UNMAP at the end of a live migration, or around
blockdev-set-active, takes QEMU down this way.
Handle UNMAP the way commit 095c08a7ba handles IDE TRIM: process the
descriptors in a coroutine between blk_co_start_request() and
blk_end_request() and submit them with BDRV_REQ_NO_QUEUE. A drain now
waits for the whole command instead of a single descriptor, the same
trade-off IDE makes. The coroutine is wrapped in an AIOCB that stays in
r->req.aiocb, so cancellation keeps working as before.
A failed descriptor ends the loop, and the completion applies the
rerror/werror policy as before. werror=ignore is handled inside the
loop, so the remaining descriptors are still discarded as the callback
chain did.
Signed-off-by: Denis V. Lunev <den@openvz.org>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Kevin Wolf <kwolf@redhat.com>
Cc: Hanna Reitz <hreitz@redhat.com>
Cc: qemu-stable@nongnu.org
---
hw/scsi/scsi-disk.c | 160 +++++++++++++++++++++++++++++++-------------
1 file changed, 113 insertions(+), 47 deletions(-)
diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
index c608fc56036..8383b7cbe93 100644
--- a/hw/scsi/scsi-disk.c
+++ b/hw/scsi/scsi-disk.c
@@ -1742,61 +1742,135 @@ static inline bool check_lba_range(SCSIDiskState *s,
sector_num + nb_sectors <= s->qdev.max_lba + 1);
}
-typedef struct UnmapCBData {
+typedef struct SCSIDiskCoAIOCB SCSIDiskCoAIOCB;
+
+typedef int coroutine_fn SCSIDiskCoFunc(SCSIDiskCoAIOCB *iocb);
+
+struct SCSIDiskCoAIOCB {
+ BlockAIOCB common;
SCSIDiskReq *r;
- uint8_t *inbuf;
- int count;
-} UnmapCBData;
+ SCSIDiskCoFunc *fn;
+ int ret;
+ bool canceled;
+};
+
+static void scsi_disk_co_aio_cancel(BlockAIOCB *acb)
+{
+ SCSIDiskCoAIOCB *iocb = container_of(acb, SCSIDiskCoAIOCB, common);
+
+ iocb->canceled = true;
+}
-static void scsi_unmap_complete(void *opaque, int ret);
+static const AIOCBInfo scsi_disk_co_aiocb_info = {
+ .aiocb_size = sizeof(SCSIDiskCoAIOCB),
+ .cancel_async = scsi_disk_co_aio_cancel,
+};
-static void scsi_unmap_complete_noio(UnmapCBData *data, int ret)
+static void scsi_disk_co_aio_bh(void *opaque)
{
- SCSIDiskReq *r = data->r;
- SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ SCSIDiskCoAIOCB *iocb = opaque;
+ BlockBackend *blk = iocb->r->req.dev->conf.blk;
- assert(r->req.aiocb == NULL);
+ iocb->common.cb(iocb->common.opaque, iocb->ret);
+ qemu_aio_unref(iocb);
- if (data->count > 0) {
- uint64_t sector_num = ldq_be_p(&data->inbuf[0]);
- uint32_t nb_sectors = ldl_be_p(&data->inbuf[8]) & 0xffffffffULL;
- r->sector = sector_num * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
- r->sector_count = nb_sectors * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+ /* Paired with blk_co_start_request in scsi_disk_co_aio_entry() */
+ blk_end_request(blk);
+}
- block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
- r->sector_count * BDRV_SECTOR_SIZE,
- BLOCK_ACCT_UNMAP);
+static void coroutine_fn scsi_disk_co_aio_entry(void *opaque)
+{
+ SCSIDiskCoAIOCB *iocb = opaque;
- r->req.aiocb = blk_aio_pdiscard(s->qdev.conf.blk,
- r->sector * BDRV_SECTOR_SIZE,
- r->sector_count * BDRV_SECTOR_SIZE,
- scsi_unmap_complete, data);
- data->count--;
- data->inbuf += 16;
- return;
- }
+ /* Paired with blk_end_request in scsi_disk_co_aio_bh() */
+ blk_co_start_request(iocb->r->req.dev->conf.blk);
+ iocb->ret = iocb->fn(iocb);
+ aio_bh_schedule_oneshot(qemu_get_current_aio_context(),
+ scsi_disk_co_aio_bh, iocb);
+}
- scsi_req_complete(&r->req, GOOD);
+static void scsi_disk_co_aio_complete(void *opaque, int ret)
+{
+ SCSIDiskReq *r = opaque;
+
+ assert(r->req.ctx == qemu_get_current_aio_context());
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ if (!scsi_disk_req_check_error(r, ret, true)) {
+ scsi_req_complete(&r->req, GOOD);
+ }
scsi_req_unref(&r->req);
- g_free(data);
}
-static void scsi_unmap_complete(void *opaque, int ret)
+/* fn must submit with BDRV_REQ_NO_QUEUE and stop once iocb->canceled */
+static void scsi_disk_co_aio_start(SCSIDiskReq *r, SCSIDiskCoFunc *fn)
{
- UnmapCBData *data = opaque;
- SCSIDiskReq *r = data->r;
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ SCSIDiskCoAIOCB *iocb;
- assert(r->req.aiocb != NULL);
- r->req.aiocb = NULL;
+ iocb = blk_aio_get(&scsi_disk_co_aiocb_info, s->qdev.conf.blk,
+ scsi_disk_co_aio_complete, r);
+ iocb->r = r;
+ iocb->fn = fn;
+ iocb->ret = 0;
+ iocb->canceled = false;
- if (scsi_disk_req_check_error(r, ret, true)) {
- scsi_req_unref(&r->req);
- g_free(data);
- } else {
- block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
- scsi_unmap_complete_noio(data, ret);
+ /* The matching unref is in scsi_disk_co_aio_complete */
+ scsi_req_ref(&r->req);
+ r->req.aiocb = &iocb->common;
+ aio_co_enter(qemu_get_current_aio_context(),
+ qemu_coroutine_create(scsi_disk_co_aio_entry, iocb));
+}
+
+/* Report like scsi_handle_rw_error(), but let werror=ignore go on */
+static bool scsi_disk_ignore_error(SCSIDiskReq *r, int ret)
+{
+ BlockBackend *blk = r->req.dev->conf.blk;
+ BlockErrorAction action = blk_get_error_action(blk, false, -ret);
+
+ if (action != BLOCK_ERROR_ACTION_IGNORE) {
+ return false;
+ }
+ blk_error_action(blk, action, false, r->sector << BDRV_SECTOR_BITS,
+ r->sector_count << BDRV_SECTOR_BITS, -ret);
+ return true;
+}
+
+static int coroutine_fn scsi_unmap_co(SCSIDiskCoAIOCB *iocb)
+{
+ SCSIDiskReq *r = iocb->r;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ BlockBackend *blk = s->qdev.conf.blk;
+ uint8_t *p = r->iov.iov_base;
+ int count = lduw_be_p(&p[2]) >> 4;
+ int i, ret;
+
+ for (i = 0; i < count; i++) {
+ uint64_t sector_num = ldq_be_p(&p[8 + i * 16]);
+ uint32_t nb_sectors = ldl_be_p(&p[8 + i * 16 + 8]);
+
+ if (iocb->canceled) {
+ return -ECANCELED;
+ }
+
+ r->sector = sector_num * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+ r->sector_count = nb_sectors * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+
+ block_acct_start(blk_get_stats(blk), &r->acct,
+ r->sector_count * BDRV_SECTOR_SIZE,
+ BLOCK_ACCT_UNMAP);
+
+ ret = blk_co_pdiscard(blk, r->sector * BDRV_SECTOR_SIZE,
+ r->sector_count * BDRV_SECTOR_SIZE,
+ BDRV_REQ_NO_QUEUE);
+ if (ret < 0 && !scsi_disk_ignore_error(r, ret)) {
+ return ret;
+ }
+ block_acct_done(blk_get_stats(blk), &r->acct);
}
+
+ return 0;
}
static void scsi_disk_emulate_unmap(SCSIDiskReq *r, uint8_t *inbuf)
@@ -1804,7 +1878,6 @@ static void scsi_disk_emulate_unmap(SCSIDiskReq *r, uint8_t *inbuf)
SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
uint8_t *p = inbuf;
int len = r->req.cmd.xfer;
- UnmapCBData *data;
int count;
int i;
@@ -1843,14 +1916,7 @@ static void scsi_disk_emulate_unmap(SCSIDiskReq *r, uint8_t *inbuf)
}
}
- data = g_new0(UnmapCBData, 1);
- data->r = r;
- data->inbuf = &p[8];
- data->count = count;
-
- /* The matching unref is in scsi_unmap_complete, before data is freed. */
- scsi_req_ref(&r->req);
- scsi_unmap_complete_noio(data, 0);
+ scsi_disk_co_aio_start(r, scsi_unmap_co);
return;
invalid_param_len:
--
2.53.0
next prev parent reply other threads:[~2026-10-26 17:49 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-26 17:49 [PATCH hci-8.1 0/5] scsi-disk: fix UNMAP and WRITE SAME racing with inactivation #VSTOR-151532 Denis V. Lunev
2026-10-26 17:49 ` [PATCH hci-8.1 1/5] scsi-disk: check all UNMAP descriptors before discarding any #VSTOR-151532 Denis V. Lunev
2026-10-26 17:49 ` Denis V. Lunev [this message]
2026-10-26 17:49 ` [PATCH hci-8.1 3/5] scsi-disk: fix WRITE SAME racing with inactivation of block nodes #VSTOR-151532 Denis V. Lunev
2026-10-26 17:49 ` [PATCH hci-8.1 4/5] virtio-scsi-test: split virtio_scsi_do_command() into submit and wait #VSTOR-151532 Denis V. Lunev
2026-10-26 17:49 ` [PATCH hci-8.1 5/5] virtio-scsi-test: test UNMAP and WRITE SAME during inactivation #VSTOR-151532 Denis V. Lunev
2026-10-27 10:56 ` [PATCH hci-8.1 0/5] scsi-disk: fix UNMAP and WRITE SAME racing with " Andrey Drobyshev
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=20261026174929.685720-3-den@openvz.org \
--to=den@openvz.org \
--cc=andrey.drobyshev@virtuozzo.com \
--cc=svt-core@virtuozzo.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 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.