diff mbox

[19/19] block: make accounting thread-safe

Message ID 20170605123908.18777-20-pbonzini@redhat.com
State New
Headers show

Commit Message

Paolo Bonzini June 5, 2017, 12:39 p.m. UTC
I'm not trying too hard yet.  Later, with multiqueue support,
this may cause mutex contention or cacheline bouncing.

Cc: Alberto Garcia <berto@igalia.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 block/accounting.c         | 16 ++++++++++++++++
 include/block/accounting.h |  8 ++++++--
 2 files changed, 22 insertions(+), 2 deletions(-)

Comments

Alberto Garcia June 6, 2017, 3:45 p.m. UTC | #1
On Mon 05 Jun 2017 02:39:08 PM CEST, Paolo Bonzini wrote:
> I'm not trying too hard yet.  Later, with multiqueue support,
> this may cause mutex contention or cacheline bouncing.
>
> Cc: Alberto Garcia <berto@igalia.com>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---

Reviewed-by: Alberto Garcia <berto@igalia.com>

Berto
Alberto Garcia May 24, 2018, 1:35 p.m. UTC | #2
On Mon 05 Jun 2017 02:39:08 PM CEST, Paolo Bonzini wrote:
> @@ -102,6 +107,8 @@ static void block_account_one_io(BlockAcctStats *stats, BlockAcctCookie *cookie,
>  
>      assert(cookie->type < BLOCK_MAX_IOTYPE);
>  
> +    qemu_mutex_lock(&stats->lock);

QEMU crashes because this lock is not initialized if you do aio_read
directly from a BDS not attached to a BlockBackend (should that
operation be legal at all?)

$ qemu-system-x86_64 -monitor stdio \
-blockdev driver=qcow2,file.driver=file,file.filename=hd0.img,node-name=hd0

(qemu) qemu-io hd0 "aio_read 0 1M"
qemu-system-x86_64: util/qemu-thread-posix.c:64: qemu_mutex_lock_impl: Assertion `mutex->initialized' failed.
Aborted

Berto
Alberto Garcia May 24, 2018, 1:49 p.m. UTC | #3
On Thu 24 May 2018 03:35:50 PM CEST, Alberto Garcia wrote:
> On Mon 05 Jun 2017 02:39:08 PM CEST, Paolo Bonzini wrote:
>> @@ -102,6 +107,8 @@ static void block_account_one_io(BlockAcctStats *stats, BlockAcctCookie *cookie,
>>  
>>      assert(cookie->type < BLOCK_MAX_IOTYPE);
>>  
>> +    qemu_mutex_lock(&stats->lock);
>
> QEMU crashes because this lock is not initialized if you do aio_read
> directly from a BDS not attached to a BlockBackend (should that
> operation be legal at all?)

I see, hmp_qemu_io() creates a temporary BlockBackend in these
situations, so I guess what we need is something like this:

diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c
index 9b3cd00af6..7b195ec4e3 100644
--- a/qemu-io-cmds.c
+++ b/qemu-io-cmds.c
@@ -1294,6 +1294,7 @@ static void aio_read_done(void *opaque, int ret)
     print_report("read", &t2, ctx->offset, ctx->qiov.size,
                  ctx->qiov.size, 1, ctx->Cflag);
 out:
+    blk_unref(ctx->blk);
     qemu_io_free(ctx->buf);
     qemu_iovec_destroy(&ctx->qiov);
     g_free(ctx);
@@ -1392,6 +1393,7 @@ static int aio_read_f(BlockBackend *blk, int argc, char **argv)
     gettimeofday(&ctx->t1, NULL);
     block_acct_start(blk_get_stats(blk), &ctx->acct, ctx->qiov.size,
                      BLOCK_ACCT_READ);
+    blk_ref(blk);
     blk_aio_preadv(blk, ctx->offset, &ctx->qiov, 0, aio_read_done,
     ctx);
     return 0;
 }

Berto
Paolo Bonzini May 24, 2018, 1:49 p.m. UTC | #4
On 24/05/2018 15:35, Alberto Garcia wrote:
> On Mon 05 Jun 2017 02:39:08 PM CEST, Paolo Bonzini wrote:
>> @@ -102,6 +107,8 @@ static void block_account_one_io(BlockAcctStats *stats, BlockAcctCookie *cookie,
>>  
>>      assert(cookie->type < BLOCK_MAX_IOTYPE);
>>  
>> +    qemu_mutex_lock(&stats->lock);
> 
> QEMU crashes because this lock is not initialized if you do aio_read
> directly from a BDS not attached to a BlockBackend (should that
> operation be legal at all?)
> 
> $ qemu-system-x86_64 -monitor stdio \
> -blockdev driver=qcow2,file.driver=file,file.filename=hd0.img,node-name=hd0
> 
> (qemu) qemu-io hd0 "aio_read 0 1M"
> qemu-system-x86_64: util/qemu-thread-posix.c:64: qemu_mutex_lock_impl: Assertion `mutex->initialized' failed.
> Aborted

The lock is initialized but it has been destroyed via qemu_mutex_destroy
before you reach this point.  So the bug is actually much more than just
the uninitialized lock!

The destruction happens when blk_delete calls block_acct_cleanup.  It
should be fixed by Greg Kurz's patch that adds a bdrv_drained_begin/end
pair to bdrv_root_unref_child.

Thanks,

Paolo
diff mbox

Patch

diff --git a/block/accounting.c b/block/accounting.c
index ce6dbf7760..87ef5bbfaa 100644
--- a/block/accounting.c
+++ b/block/accounting.c
@@ -34,6 +34,7 @@  static const int qtest_latency_ns = NANOSECONDS_PER_SECOND / 1000;
 
 void block_acct_init(BlockAcctStats *stats)
 {
+    qemu_mutex_init(&stats->lock);
     if (qtest_enabled()) {
         clock_type = QEMU_CLOCK_VIRTUAL;
     }
@@ -52,6 +53,7 @@  void block_acct_cleanup(BlockAcctStats *stats)
     QSLIST_FOREACH_SAFE(s, &stats->intervals, entries, next) {
         g_free(s);
     }
+    qemu_mutex_destroy(&stats->lock);
 }
 
 void block_acct_add_interval(BlockAcctStats *stats, unsigned interval_length)
@@ -61,12 +63,15 @@  void block_acct_add_interval(BlockAcctStats *stats, unsigned interval_length)
 
     s = g_new0(BlockAcctTimedStats, 1);
     s->interval_length = interval_length;
+    s->stats = stats;
+    qemu_mutex_lock(&stats->lock);
     QSLIST_INSERT_HEAD(&stats->intervals, s, entries);
 
     for (i = 0; i < BLOCK_MAX_IOTYPE; i++) {
         timed_average_init(&s->latency[i], clock_type,
                            (uint64_t) interval_length * NANOSECONDS_PER_SECOND);
     }
+    qemu_mutex_unlock(&stats->lock);
 }
 
 BlockAcctTimedStats *block_acct_interval_next(BlockAcctStats *stats,
@@ -102,6 +107,8 @@  static void block_account_one_io(BlockAcctStats *stats, BlockAcctCookie *cookie,
 
     assert(cookie->type < BLOCK_MAX_IOTYPE);
 
+    qemu_mutex_lock(&stats->lock);
+
     if (failed) {
         stats->failed_ops[cookie->type]++;
     } else {
@@ -117,6 +124,8 @@  static void block_account_one_io(BlockAcctStats *stats, BlockAcctCookie *cookie,
             timed_average_account(&s->latency[cookie->type], latency_ns);
         }
     }
+
+    qemu_mutex_unlock(&stats->lock);
 }
 
 void block_acct_done(BlockAcctStats *stats, BlockAcctCookie *cookie)
@@ -137,18 +146,23 @@  void block_acct_invalid(BlockAcctStats *stats, enum BlockAcctType type)
      * not.  The reason is that invalid requests are accounted during their
      * submission, therefore there's no actual I/O involved.
      */
+    qemu_mutex_lock(&stats->lock);
     stats->invalid_ops[type]++;
 
     if (stats->account_invalid) {
         stats->last_access_time_ns = qemu_clock_get_ns(clock_type);
     }
+    qemu_mutex_unlock(&stats->lock);
 }
 
 void block_acct_merge_done(BlockAcctStats *stats, enum BlockAcctType type,
                       int num_requests)
 {
     assert(type < BLOCK_MAX_IOTYPE);
+
+    qemu_mutex_lock(&stats->lock);
     stats->merged[type] += num_requests;
+    qemu_mutex_unlock(&stats->lock);
 }
 
 int64_t block_acct_idle_time_ns(BlockAcctStats *stats)
@@ -163,7 +177,9 @@  double block_acct_queue_depth(BlockAcctTimedStats *stats,
 
     assert(type < BLOCK_MAX_IOTYPE);
 
+    qemu_mutex_lock(&stats->stats->lock);
     sum = timed_average_sum(&stats->latency[type], &elapsed);
+    qemu_mutex_unlock(&stats->stats->lock);
 
     return (double) sum / elapsed;
 }
diff --git a/include/block/accounting.h b/include/block/accounting.h
index 55cb06fdb6..b833d26d6c 100644
--- a/include/block/accounting.h
+++ b/include/block/accounting.h
@@ -26,8 +26,10 @@ 
 #define BLOCK_ACCOUNTING_H
 
 #include "qemu/timed-average.h"
+#include "qemu/thread.h"
 
 typedef struct BlockAcctTimedStats BlockAcctTimedStats;
+typedef struct BlockAcctStats BlockAcctStats;
 
 enum BlockAcctType {
     BLOCK_ACCT_READ,
@@ -37,12 +39,14 @@  enum BlockAcctType {
 };
 
 struct BlockAcctTimedStats {
+    BlockAcctStats *stats;
     TimedAverage latency[BLOCK_MAX_IOTYPE];
     unsigned interval_length; /* in seconds */
     QSLIST_ENTRY(BlockAcctTimedStats) entries;
 };
 
-typedef struct BlockAcctStats {
+struct BlockAcctStats {
+    QemuMutex lock;
     uint64_t nr_bytes[BLOCK_MAX_IOTYPE];
     uint64_t nr_ops[BLOCK_MAX_IOTYPE];
     uint64_t invalid_ops[BLOCK_MAX_IOTYPE];
@@ -53,7 +57,7 @@  typedef struct BlockAcctStats {
     QSLIST_HEAD(, BlockAcctTimedStats) intervals;
     bool account_invalid;
     bool account_failed;
-} BlockAcctStats;
+};
 
 typedef struct BlockAcctCookie {
     int64_t bytes;