qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] block: protect BlockBackend->queued_requests with a lock
@ 2023-02-27 20:57 Stefan Hajnoczi
  2023-02-27 20:57 ` [PATCH 1/3] block: make BlockBackend->quiesce_counter atomic Stefan Hajnoczi
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Stefan Hajnoczi @ 2023-02-27 20:57 UTC (permalink / raw)
  To: qemu-devel
  Cc: Emanuele Giuseppe Esposito, qemu-block, Hanna Reitz, Kevin Wolf,
	Paolo Bonzini, Stefan Hajnoczi

QEMU block layer multi-queue support involves running I/O requests from
multiple threads. Shared state must be protected somehow to avoid thread-safety
issues.

The BlockBackend->queued_requests CoQueue is accessed without a lock and will
likely be corrupted when multiple threads queue requests at the same time.

This patch series make BlockBackend->queued_requests thread-safe.

Stefan Hajnoczi (3):
  block: make BlockBackend->quiesce_counter atomic
  block: make BlockBackend->disable_request_queuing atomic
  block: protect BlockBackend->queued_requests with a lock

 block/block-backend.c | 41 ++++++++++++++++++++++++++++++-----------
 1 file changed, 30 insertions(+), 11 deletions(-)

-- 
2.39.2



^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/3] block: make BlockBackend->quiesce_counter atomic
  2023-02-27 20:57 [PATCH 0/3] block: protect BlockBackend->queued_requests with a lock Stefan Hajnoczi
@ 2023-02-27 20:57 ` Stefan Hajnoczi
  2023-03-03 15:29   ` Hanna Czenczek
  2023-02-27 20:57 ` [PATCH 2/3] block: make BlockBackend->disable_request_queuing atomic Stefan Hajnoczi
  2023-02-27 20:57 ` [PATCH 3/3] block: protect BlockBackend->queued_requests with a lock Stefan Hajnoczi
  2 siblings, 1 reply; 8+ messages in thread
From: Stefan Hajnoczi @ 2023-02-27 20:57 UTC (permalink / raw)
  To: qemu-devel
  Cc: Emanuele Giuseppe Esposito, qemu-block, Hanna Reitz, Kevin Wolf,
	Paolo Bonzini, Stefan Hajnoczi

The main loop thread increments/decrements BlockBackend->quiesce_counter
when drained sections begin/end. The counter is read in the I/O code
path. Therefore this field is used to communicate between threads
without a lock.

Use qatomic_set()/qatomic_read() to make it clear that this field is
accessed by multiple threads.

Acquire/release are not necessary because the BlockBackend->in_flight
counter already uses sequentially consistent accesses and running I/O
requests hold that counter when blk_wait_while_drained() is called.

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 block/block-backend.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/block/block-backend.c b/block/block-backend.c
index 278b04ce69..f00bf2ab35 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -80,7 +80,7 @@ struct BlockBackend {
     NotifierList remove_bs_notifiers, insert_bs_notifiers;
     QLIST_HEAD(, BlockBackendAioNotifier) aio_notifiers;
 
-    int quiesce_counter;
+    int quiesce_counter; /* atomic: written under BQL, read by other threads */
     CoQueue queued_requests;
     bool disable_request_queuing;
 
@@ -1057,7 +1057,7 @@ void blk_set_dev_ops(BlockBackend *blk, const BlockDevOps *ops,
     blk->dev_opaque = opaque;
 
     /* Are we currently quiesced? Should we enforce this right now? */
-    if (blk->quiesce_counter && ops && ops->drained_begin) {
+    if (qatomic_read(&blk->quiesce_counter) && ops && ops->drained_begin) {
         ops->drained_begin(opaque);
     }
 }
@@ -1271,7 +1271,7 @@ static void coroutine_fn blk_wait_while_drained(BlockBackend *blk)
 {
     assert(blk->in_flight > 0);
 
-    if (blk->quiesce_counter && !blk->disable_request_queuing) {
+    if (qatomic_read(&blk->quiesce_counter) && !blk->disable_request_queuing) {
         blk_dec_in_flight(blk);
         qemu_co_queue_wait(&blk->queued_requests, NULL);
         blk_inc_in_flight(blk);
@@ -2568,7 +2568,9 @@ static void blk_root_drained_begin(BdrvChild *child)
     BlockBackend *blk = child->opaque;
     ThrottleGroupMember *tgm = &blk->public.throttle_group_member;
 
-    if (++blk->quiesce_counter == 1) {
+    int new_counter = qatomic_read(&blk->quiesce_counter) + 1;
+    qatomic_set(&blk->quiesce_counter, new_counter);
+    if (new_counter == 1) {
         if (blk->dev_ops && blk->dev_ops->drained_begin) {
             blk->dev_ops->drained_begin(blk->dev_opaque);
         }
@@ -2586,7 +2588,7 @@ static bool blk_root_drained_poll(BdrvChild *child)
 {
     BlockBackend *blk = child->opaque;
     bool busy = false;
-    assert(blk->quiesce_counter);
+    assert(qatomic_read(&blk->quiesce_counter));
 
     if (blk->dev_ops && blk->dev_ops->drained_poll) {
         busy = blk->dev_ops->drained_poll(blk->dev_opaque);
@@ -2597,12 +2599,14 @@ static bool blk_root_drained_poll(BdrvChild *child)
 static void blk_root_drained_end(BdrvChild *child)
 {
     BlockBackend *blk = child->opaque;
-    assert(blk->quiesce_counter);
+    assert(qatomic_read(&blk->quiesce_counter));
 
     assert(blk->public.throttle_group_member.io_limits_disabled);
     qatomic_dec(&blk->public.throttle_group_member.io_limits_disabled);
 
-    if (--blk->quiesce_counter == 0) {
+    int new_counter = qatomic_read(&blk->quiesce_counter) - 1;
+    qatomic_set(&blk->quiesce_counter, new_counter);
+    if (new_counter == 0) {
         if (blk->dev_ops && blk->dev_ops->drained_end) {
             blk->dev_ops->drained_end(blk->dev_opaque);
         }
-- 
2.39.2



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/3] block: make BlockBackend->disable_request_queuing atomic
  2023-02-27 20:57 [PATCH 0/3] block: protect BlockBackend->queued_requests with a lock Stefan Hajnoczi
  2023-02-27 20:57 ` [PATCH 1/3] block: make BlockBackend->quiesce_counter atomic Stefan Hajnoczi
@ 2023-02-27 20:57 ` Stefan Hajnoczi
  2023-03-03 15:30   ` Hanna Czenczek
  2023-02-27 20:57 ` [PATCH 3/3] block: protect BlockBackend->queued_requests with a lock Stefan Hajnoczi
  2 siblings, 1 reply; 8+ messages in thread
From: Stefan Hajnoczi @ 2023-02-27 20:57 UTC (permalink / raw)
  To: qemu-devel
  Cc: Emanuele Giuseppe Esposito, qemu-block, Hanna Reitz, Kevin Wolf,
	Paolo Bonzini, Stefan Hajnoczi

This field is accessed by multiple threads without a lock. Use explicit
qatomic_read()/qatomic_set() calls. There is no need for acquire/release
because blk_set_disable_request_queuing() doesn't provide any
guarantees (it helps that it's used at BlockBackend creation time and
not when there is I/O in flight).

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 block/block-backend.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/block/block-backend.c b/block/block-backend.c
index f00bf2ab35..ec7eafd7fb 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -82,7 +82,7 @@ struct BlockBackend {
 
     int quiesce_counter; /* atomic: written under BQL, read by other threads */
     CoQueue queued_requests;
-    bool disable_request_queuing;
+    bool disable_request_queuing; /* atomic */
 
     VMChangeStateEntry *vmsh;
     bool force_allow_inactivate;
@@ -1232,7 +1232,7 @@ void blk_set_allow_aio_context_change(BlockBackend *blk, bool allow)
 void blk_set_disable_request_queuing(BlockBackend *blk, bool disable)
 {
     IO_CODE();
-    blk->disable_request_queuing = disable;
+    qatomic_set(&blk->disable_request_queuing, disable);
 }
 
 static int coroutine_fn GRAPH_RDLOCK
@@ -1271,7 +1271,8 @@ static void coroutine_fn blk_wait_while_drained(BlockBackend *blk)
 {
     assert(blk->in_flight > 0);
 
-    if (qatomic_read(&blk->quiesce_counter) && !blk->disable_request_queuing) {
+    if (qatomic_read(&blk->quiesce_counter) &&
+        !qatomic_read(&blk->disable_request_queuing)) {
         blk_dec_in_flight(blk);
         qemu_co_queue_wait(&blk->queued_requests, NULL);
         blk_inc_in_flight(blk);
-- 
2.39.2



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 3/3] block: protect BlockBackend->queued_requests with a lock
  2023-02-27 20:57 [PATCH 0/3] block: protect BlockBackend->queued_requests with a lock Stefan Hajnoczi
  2023-02-27 20:57 ` [PATCH 1/3] block: make BlockBackend->quiesce_counter atomic Stefan Hajnoczi
  2023-02-27 20:57 ` [PATCH 2/3] block: make BlockBackend->disable_request_queuing atomic Stefan Hajnoczi
@ 2023-02-27 20:57 ` Stefan Hajnoczi
  2023-03-03 15:30   ` Hanna Czenczek
  2 siblings, 1 reply; 8+ messages in thread
From: Stefan Hajnoczi @ 2023-02-27 20:57 UTC (permalink / raw)
  To: qemu-devel
  Cc: Emanuele Giuseppe Esposito, qemu-block, Hanna Reitz, Kevin Wolf,
	Paolo Bonzini, Stefan Hajnoczi

The CoQueue API offers thread-safety via the lock argument that
qemu_co_queue_wait() and qemu_co_enter_next() take. BlockBackend
currently does not make use of the lock argument. This means that
multiple threads submitting I/O requests can corrupt the CoQueue's
QSIMPLEQ.

Add a QemuMutex and pass it to CoQueue APIs so that the queue is
protected. While we're at it, also assert that the queue is empty when
the BlockBackend is deleted.

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 block/block-backend.c | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/block/block-backend.c b/block/block-backend.c
index ec7eafd7fb..c4dd16f4da 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -81,6 +81,7 @@ struct BlockBackend {
     QLIST_HEAD(, BlockBackendAioNotifier) aio_notifiers;
 
     int quiesce_counter; /* atomic: written under BQL, read by other threads */
+    QemuMutex queued_requests_lock; /* protects queued_requests */
     CoQueue queued_requests;
     bool disable_request_queuing; /* atomic */
 
@@ -368,6 +369,7 @@ BlockBackend *blk_new(AioContext *ctx, uint64_t perm, uint64_t shared_perm)
 
     block_acct_init(&blk->stats);
 
+    qemu_mutex_init(&blk->queued_requests_lock);
     qemu_co_queue_init(&blk->queued_requests);
     notifier_list_init(&blk->remove_bs_notifiers);
     notifier_list_init(&blk->insert_bs_notifiers);
@@ -485,6 +487,8 @@ static void blk_delete(BlockBackend *blk)
     assert(QLIST_EMPTY(&blk->remove_bs_notifiers.notifiers));
     assert(QLIST_EMPTY(&blk->insert_bs_notifiers.notifiers));
     assert(QLIST_EMPTY(&blk->aio_notifiers));
+    assert(qemu_co_queue_empty(&blk->queued_requests));
+    qemu_mutex_destroy(&blk->queued_requests_lock);
     QTAILQ_REMOVE(&block_backends, blk, link);
     drive_info_del(blk->legacy_dinfo);
     block_acct_cleanup(&blk->stats);
@@ -1273,9 +1277,16 @@ static void coroutine_fn blk_wait_while_drained(BlockBackend *blk)
 
     if (qatomic_read(&blk->quiesce_counter) &&
         !qatomic_read(&blk->disable_request_queuing)) {
+        /*
+         * Take lock before decrementing in flight counter so main loop thread
+         * waits for us to enqueue ourselves before it can leave the drained
+         * section.
+         */
+        qemu_mutex_lock(&blk->queued_requests_lock);
         blk_dec_in_flight(blk);
-        qemu_co_queue_wait(&blk->queued_requests, NULL);
+        qemu_co_queue_wait(&blk->queued_requests, &blk->queued_requests_lock);
         blk_inc_in_flight(blk);
+        qemu_mutex_unlock(&blk->queued_requests_lock);
     }
 }
 
@@ -2611,9 +2622,12 @@ static void blk_root_drained_end(BdrvChild *child)
         if (blk->dev_ops && blk->dev_ops->drained_end) {
             blk->dev_ops->drained_end(blk->dev_opaque);
         }
-        while (qemu_co_enter_next(&blk->queued_requests, NULL)) {
+        qemu_mutex_lock(&blk->queued_requests_lock);
+        while (qemu_co_enter_next(&blk->queued_requests,
+                                  &blk->queued_requests_lock)) {
             /* Resume all queued requests */
         }
+        qemu_mutex_unlock(&blk->queued_requests_lock);
     }
 }
 
-- 
2.39.2



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/3] block: make BlockBackend->quiesce_counter atomic
  2023-02-27 20:57 ` [PATCH 1/3] block: make BlockBackend->quiesce_counter atomic Stefan Hajnoczi
@ 2023-03-03 15:29   ` Hanna Czenczek
  2023-03-06 21:01     ` Stefan Hajnoczi
  0 siblings, 1 reply; 8+ messages in thread
From: Hanna Czenczek @ 2023-03-03 15:29 UTC (permalink / raw)
  To: Stefan Hajnoczi, qemu-devel
  Cc: Emanuele Giuseppe Esposito, qemu-block, Kevin Wolf, Paolo Bonzini

On 27.02.23 21:57, Stefan Hajnoczi wrote:
> The main loop thread increments/decrements BlockBackend->quiesce_counter
> when drained sections begin/end. The counter is read in the I/O code
> path. Therefore this field is used to communicate between threads
> without a lock.
>
> Use qatomic_set()/qatomic_read() to make it clear that this field is
> accessed by multiple threads.
>
> Acquire/release are not necessary because the BlockBackend->in_flight
> counter already uses sequentially consistent accesses and running I/O
> requests hold that counter when blk_wait_while_drained() is called.
>
> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
>   block/block-backend.c | 18 +++++++++++-------
>   1 file changed, 11 insertions(+), 7 deletions(-)
>
> diff --git a/block/block-backend.c b/block/block-backend.c
> index 278b04ce69..f00bf2ab35 100644
> --- a/block/block-backend.c
> +++ b/block/block-backend.c

[...]

> @@ -2568,7 +2568,9 @@ static void blk_root_drained_begin(BdrvChild *child)
>       BlockBackend *blk = child->opaque;
>       ThrottleGroupMember *tgm = &blk->public.throttle_group_member;
>   
> -    if (++blk->quiesce_counter == 1) {
> +    int new_counter = qatomic_read(&blk->quiesce_counter) + 1;
> +    qatomic_set(&blk->quiesce_counter, new_counter);
> +    if (new_counter == 1) {
>           if (blk->dev_ops && blk->dev_ops->drained_begin) {
>               blk->dev_ops->drained_begin(blk->dev_opaque);
>           }

[...]

> @@ -2597,12 +2599,14 @@ static bool blk_root_drained_poll(BdrvChild *child)

[...]

>       assert(blk->public.throttle_group_member.io_limits_disabled);
>       qatomic_dec(&blk->public.throttle_group_member.io_limits_disabled);
>   
> -    if (--blk->quiesce_counter == 0) {
> +    int new_counter = qatomic_read(&blk->quiesce_counter) - 1;
> +    qatomic_set(&blk->quiesce_counter, new_counter);

I don’t quite understand why you decided not to use simple atomic 
increments/decrements with just SeqCst in these places.  Maybe it is 
fine this way, but it isn’t trivial to see.  As far as I understand, 
these aren’t hot paths, so I don’t think we’d lose performance by using 
fully atomic operations here.

Hanna



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 2/3] block: make BlockBackend->disable_request_queuing atomic
  2023-02-27 20:57 ` [PATCH 2/3] block: make BlockBackend->disable_request_queuing atomic Stefan Hajnoczi
@ 2023-03-03 15:30   ` Hanna Czenczek
  0 siblings, 0 replies; 8+ messages in thread
From: Hanna Czenczek @ 2023-03-03 15:30 UTC (permalink / raw)
  To: Stefan Hajnoczi, qemu-devel
  Cc: Emanuele Giuseppe Esposito, qemu-block, Kevin Wolf, Paolo Bonzini

On 27.02.23 21:57, Stefan Hajnoczi wrote:
> This field is accessed by multiple threads without a lock. Use explicit
> qatomic_read()/qatomic_set() calls. There is no need for acquire/release
> because blk_set_disable_request_queuing() doesn't provide any
> guarantees (it helps that it's used at BlockBackend creation time and
> not when there is I/O in flight).
>
> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
>   block/block-backend.c | 7 ++++---
>   1 file changed, 4 insertions(+), 3 deletions(-)

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 3/3] block: protect BlockBackend->queued_requests with a lock
  2023-02-27 20:57 ` [PATCH 3/3] block: protect BlockBackend->queued_requests with a lock Stefan Hajnoczi
@ 2023-03-03 15:30   ` Hanna Czenczek
  0 siblings, 0 replies; 8+ messages in thread
From: Hanna Czenczek @ 2023-03-03 15:30 UTC (permalink / raw)
  To: Stefan Hajnoczi, qemu-devel
  Cc: Emanuele Giuseppe Esposito, qemu-block, Kevin Wolf, Paolo Bonzini

On 27.02.23 21:57, Stefan Hajnoczi wrote:
> The CoQueue API offers thread-safety via the lock argument that
> qemu_co_queue_wait() and qemu_co_enter_next() take. BlockBackend
> currently does not make use of the lock argument. This means that
> multiple threads submitting I/O requests can corrupt the CoQueue's
> QSIMPLEQ.
>
> Add a QemuMutex and pass it to CoQueue APIs so that the queue is
> protected. While we're at it, also assert that the queue is empty when
> the BlockBackend is deleted.
>
> Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
>   block/block-backend.c | 18 ++++++++++++++++--
>   1 file changed, 16 insertions(+), 2 deletions(-)

Reviewed-by: Hanna Czenczek <hreitz@redhat.com>



^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/3] block: make BlockBackend->quiesce_counter atomic
  2023-03-03 15:29   ` Hanna Czenczek
@ 2023-03-06 21:01     ` Stefan Hajnoczi
  0 siblings, 0 replies; 8+ messages in thread
From: Stefan Hajnoczi @ 2023-03-06 21:01 UTC (permalink / raw)
  To: Hanna Czenczek
  Cc: qemu-devel, Emanuele Giuseppe Esposito, qemu-block, Kevin Wolf,
	Paolo Bonzini

[-- Attachment #1: Type: text/plain, Size: 2463 bytes --]

On Fri, Mar 03, 2023 at 04:29:54PM +0100, Hanna Czenczek wrote:
> On 27.02.23 21:57, Stefan Hajnoczi wrote:
> > The main loop thread increments/decrements BlockBackend->quiesce_counter
> > when drained sections begin/end. The counter is read in the I/O code
> > path. Therefore this field is used to communicate between threads
> > without a lock.
> > 
> > Use qatomic_set()/qatomic_read() to make it clear that this field is
> > accessed by multiple threads.
> > 
> > Acquire/release are not necessary because the BlockBackend->in_flight
> > counter already uses sequentially consistent accesses and running I/O
> > requests hold that counter when blk_wait_while_drained() is called.
> > 
> > Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
> > ---
> >   block/block-backend.c | 18 +++++++++++-------
> >   1 file changed, 11 insertions(+), 7 deletions(-)
> > 
> > diff --git a/block/block-backend.c b/block/block-backend.c
> > index 278b04ce69..f00bf2ab35 100644
> > --- a/block/block-backend.c
> > +++ b/block/block-backend.c
> 
> [...]
> 
> > @@ -2568,7 +2568,9 @@ static void blk_root_drained_begin(BdrvChild *child)
> >       BlockBackend *blk = child->opaque;
> >       ThrottleGroupMember *tgm = &blk->public.throttle_group_member;
> > -    if (++blk->quiesce_counter == 1) {
> > +    int new_counter = qatomic_read(&blk->quiesce_counter) + 1;
> > +    qatomic_set(&blk->quiesce_counter, new_counter);
> > +    if (new_counter == 1) {
> >           if (blk->dev_ops && blk->dev_ops->drained_begin) {
> >               blk->dev_ops->drained_begin(blk->dev_opaque);
> >           }
> 
> [...]
> 
> > @@ -2597,12 +2599,14 @@ static bool blk_root_drained_poll(BdrvChild *child)
> 
> [...]
> 
> >       assert(blk->public.throttle_group_member.io_limits_disabled);
> >       qatomic_dec(&blk->public.throttle_group_member.io_limits_disabled);
> > -    if (--blk->quiesce_counter == 0) {
> > +    int new_counter = qatomic_read(&blk->quiesce_counter) - 1;
> > +    qatomic_set(&blk->quiesce_counter, new_counter);
> 
> I don’t quite understand why you decided not to use simple atomic
> increments/decrements with just SeqCst in these places.  Maybe it is fine
> this way, but it isn’t trivial to see.  As far as I understand, these aren’t
> hot paths, so I don’t think we’d lose performance by using fully atomic
> operations here.

Good idea. It would be much easier to read.

Stefan

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2023-03-06 21:01 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-02-27 20:57 [PATCH 0/3] block: protect BlockBackend->queued_requests with a lock Stefan Hajnoczi
2023-02-27 20:57 ` [PATCH 1/3] block: make BlockBackend->quiesce_counter atomic Stefan Hajnoczi
2023-03-03 15:29   ` Hanna Czenczek
2023-03-06 21:01     ` Stefan Hajnoczi
2023-02-27 20:57 ` [PATCH 2/3] block: make BlockBackend->disable_request_queuing atomic Stefan Hajnoczi
2023-03-03 15:30   ` Hanna Czenczek
2023-02-27 20:57 ` [PATCH 3/3] block: protect BlockBackend->queued_requests with a lock Stefan Hajnoczi
2023-03-03 15:30   ` Hanna Czenczek

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).