From: "Denis V. Lunev" <den@openvz.org>
To: qemu-block@nongnu.org, qemu-devel@nongnu.org
Cc: den@openvz.org, Kevin Wolf <kwolf@redhat.com>,
Hanna Reitz <hreitz@redhat.com>,
Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Subject: [PATCH 2/3] block/qapi: take stats->lock when reading BlockAcctStats for query-blockstats
Date: Thu, 23 Jul 2026 14:37:44 +0200 [thread overview]
Message-ID: <20260723123745.3294227-3-den@openvz.org> (raw)
In-Reply-To: <20260723123745.3294227-1-den@openvz.org>
bdrv_query_blk_stats() reads nr_bytes[], nr_ops[], failed_ops[],
invalid_ops[], merged[], total_time_ns[], last_access_time_ns, and
the latency histogram bins directly off BlockAcctStats, without
taking stats->lock. block_account_one_io() updates the same fields
under that lock from whatever iothread completes the I/O, so this is
an unsynchronized read of concurrently-updated state.
This is a narrower issue than the one fixed for the histogram
setters. query-blockstats and block-latency-histogram-set are both
plain (non-allow-oob) QMP commands, so they always run serialized on
the monitor thread and can never execute concurrently with each
other.
Take stats->lock around the read for consistency.
Signed-off-by: Denis V. Lunev <den@openvz.org>
CC: Kevin Wolf <kwolf@redhat.com>
CC: Hanna Reitz <hreitz@redhat.com>
CC: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
---
block/qapi.c | 102 ++++++++++++++++++++++++++++++---------------------
1 file changed, 60 insertions(+), 42 deletions(-)
diff --git a/block/qapi.c b/block/qapi.c
index eabfbfc258..c3f20832b7 100644
--- a/block/qapi.c
+++ b/block/qapi.c
@@ -535,49 +535,67 @@ static void bdrv_query_blk_stats(BlockDeviceStats *ds, BlockBackend *blk)
BlockAcctTimedStats *ts = NULL;
BlockLatencyHistogram *hgram;
- ds->rd_bytes = stats->nr_bytes[BLOCK_ACCT_READ];
- ds->wr_bytes = stats->nr_bytes[BLOCK_ACCT_WRITE];
- ds->zone_append_bytes = stats->nr_bytes[BLOCK_ACCT_ZONE_APPEND];
- ds->unmap_bytes = stats->nr_bytes[BLOCK_ACCT_UNMAP];
- ds->rd_operations = stats->nr_ops[BLOCK_ACCT_READ];
- ds->wr_operations = stats->nr_ops[BLOCK_ACCT_WRITE];
- ds->zone_append_operations = stats->nr_ops[BLOCK_ACCT_ZONE_APPEND];
- ds->unmap_operations = stats->nr_ops[BLOCK_ACCT_UNMAP];
-
- ds->failed_rd_operations = stats->failed_ops[BLOCK_ACCT_READ];
- ds->failed_wr_operations = stats->failed_ops[BLOCK_ACCT_WRITE];
- ds->failed_zone_append_operations =
- stats->failed_ops[BLOCK_ACCT_ZONE_APPEND];
- ds->failed_flush_operations = stats->failed_ops[BLOCK_ACCT_FLUSH];
- ds->failed_unmap_operations = stats->failed_ops[BLOCK_ACCT_UNMAP];
-
- ds->invalid_rd_operations = stats->invalid_ops[BLOCK_ACCT_READ];
- ds->invalid_wr_operations = stats->invalid_ops[BLOCK_ACCT_WRITE];
- ds->invalid_zone_append_operations =
- stats->invalid_ops[BLOCK_ACCT_ZONE_APPEND];
- ds->invalid_flush_operations =
- stats->invalid_ops[BLOCK_ACCT_FLUSH];
- ds->invalid_unmap_operations = stats->invalid_ops[BLOCK_ACCT_UNMAP];
-
- ds->rd_merged = stats->merged[BLOCK_ACCT_READ];
- ds->wr_merged = stats->merged[BLOCK_ACCT_WRITE];
- ds->zone_append_merged = stats->merged[BLOCK_ACCT_ZONE_APPEND];
- ds->unmap_merged = stats->merged[BLOCK_ACCT_UNMAP];
- ds->flush_operations = stats->nr_ops[BLOCK_ACCT_FLUSH];
- ds->wr_total_time_ns = stats->total_time_ns[BLOCK_ACCT_WRITE];
- ds->zone_append_total_time_ns =
- stats->total_time_ns[BLOCK_ACCT_ZONE_APPEND];
- ds->rd_total_time_ns = stats->total_time_ns[BLOCK_ACCT_READ];
- ds->flush_total_time_ns = stats->total_time_ns[BLOCK_ACCT_FLUSH];
- ds->unmap_total_time_ns = stats->total_time_ns[BLOCK_ACCT_UNMAP];
-
- ds->has_idle_time_ns = stats->last_access_time_ns > 0;
- if (ds->has_idle_time_ns) {
- ds->idle_time_ns = block_acct_idle_time_ns(stats);
- }
+ /*
+ * nr_bytes[] etc. are mutated under stats->lock by block_account_one_io()
+ * from an iothread, and latency_histogram[].{nbins,boundaries,bins} can
+ * be freed and reallocated by a concurrent block_latency_histogram_set()
+ * on the monitor thread, so every read here needs the same lock.
+ */
+ WITH_QEMU_LOCK_GUARD(&stats->lock) {
+ ds->rd_bytes = stats->nr_bytes[BLOCK_ACCT_READ];
+ ds->wr_bytes = stats->nr_bytes[BLOCK_ACCT_WRITE];
+ ds->zone_append_bytes = stats->nr_bytes[BLOCK_ACCT_ZONE_APPEND];
+ ds->unmap_bytes = stats->nr_bytes[BLOCK_ACCT_UNMAP];
+ ds->rd_operations = stats->nr_ops[BLOCK_ACCT_READ];
+ ds->wr_operations = stats->nr_ops[BLOCK_ACCT_WRITE];
+ ds->zone_append_operations = stats->nr_ops[BLOCK_ACCT_ZONE_APPEND];
+ ds->unmap_operations = stats->nr_ops[BLOCK_ACCT_UNMAP];
+
+ ds->failed_rd_operations = stats->failed_ops[BLOCK_ACCT_READ];
+ ds->failed_wr_operations = stats->failed_ops[BLOCK_ACCT_WRITE];
+ ds->failed_zone_append_operations =
+ stats->failed_ops[BLOCK_ACCT_ZONE_APPEND];
+ ds->failed_flush_operations = stats->failed_ops[BLOCK_ACCT_FLUSH];
+ ds->failed_unmap_operations = stats->failed_ops[BLOCK_ACCT_UNMAP];
+
+ ds->invalid_rd_operations = stats->invalid_ops[BLOCK_ACCT_READ];
+ ds->invalid_wr_operations = stats->invalid_ops[BLOCK_ACCT_WRITE];
+ ds->invalid_zone_append_operations =
+ stats->invalid_ops[BLOCK_ACCT_ZONE_APPEND];
+ ds->invalid_flush_operations =
+ stats->invalid_ops[BLOCK_ACCT_FLUSH];
+ ds->invalid_unmap_operations = stats->invalid_ops[BLOCK_ACCT_UNMAP];
+
+ ds->rd_merged = stats->merged[BLOCK_ACCT_READ];
+ ds->wr_merged = stats->merged[BLOCK_ACCT_WRITE];
+ ds->zone_append_merged = stats->merged[BLOCK_ACCT_ZONE_APPEND];
+ ds->unmap_merged = stats->merged[BLOCK_ACCT_UNMAP];
+ ds->flush_operations = stats->nr_ops[BLOCK_ACCT_FLUSH];
+ ds->wr_total_time_ns = stats->total_time_ns[BLOCK_ACCT_WRITE];
+ ds->zone_append_total_time_ns =
+ stats->total_time_ns[BLOCK_ACCT_ZONE_APPEND];
+ ds->rd_total_time_ns = stats->total_time_ns[BLOCK_ACCT_READ];
+ ds->flush_total_time_ns = stats->total_time_ns[BLOCK_ACCT_FLUSH];
+ ds->unmap_total_time_ns = stats->total_time_ns[BLOCK_ACCT_UNMAP];
+
+ ds->has_idle_time_ns = stats->last_access_time_ns > 0;
+ if (ds->has_idle_time_ns) {
+ ds->idle_time_ns = block_acct_idle_time_ns(stats);
+ }
- ds->account_invalid = stats->account_invalid;
- ds->account_failed = stats->account_failed;
+ ds->account_invalid = stats->account_invalid;
+ ds->account_failed = stats->account_failed;
+
+ hgram = stats->latency_histogram;
+ ds->rd_latency_histogram
+ = bdrv_latency_histogram_stats(&hgram[BLOCK_ACCT_READ]);
+ ds->wr_latency_histogram
+ = bdrv_latency_histogram_stats(&hgram[BLOCK_ACCT_WRITE]);
+ ds->zone_append_latency_histogram
+ = bdrv_latency_histogram_stats(&hgram[BLOCK_ACCT_ZONE_APPEND]);
+ ds->flush_latency_histogram
+ = bdrv_latency_histogram_stats(&hgram[BLOCK_ACCT_FLUSH]);
+ }
while ((ts = block_acct_interval_next(stats, ts))) {
BlockDeviceTimedStats *dev_stats = g_malloc0(sizeof(*dev_stats));
--
2.53.0
next prev parent reply other threads:[~2026-07-23 12:38 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-23 12:37 [PATCH 0/3] block/accounting: fix unlocked latency histogram reconfiguration races Denis V. Lunev
2026-07-23 12:37 ` [PATCH 1/3] block/accounting: take stats->lock in latency histogram setters Denis V. Lunev
2026-07-23 12:37 ` Denis V. Lunev [this message]
2026-07-23 14:02 ` [PATCH 2/3] block/qapi: take stats->lock when reading BlockAcctStats for query-blockstats Andrey Drobyshev
2026-07-23 12:37 ` [PATCH 3/3] tests/unit: add reproducer for BlockAcctStats histogram locking race Denis V. Lunev
2026-07-23 14:04 ` 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=20260723123745.3294227-3-den@openvz.org \
--to=den@openvz.org \
--cc=hreitz@redhat.com \
--cc=kwolf@redhat.com \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=vsementsov@yandex-team.ru \
/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.