All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH] mirror: Confirm we're quiesced only if the job is paused or cancelled
@ 2019-03-07 14:03 Sergio Lopez
  2019-03-07 17:15 ` Eric Blake
  0 siblings, 1 reply; 2+ messages in thread
From: Sergio Lopez @ 2019-03-07 14:03 UTC (permalink / raw)
  To: qemu-block; +Cc: qemu-devel, stefanha, kwolf, mreitz, Sergio Lopez

While child_job_drained_begin() calls to job_pause(), the job doesn't
actually transition between states until it runs again and reaches a
pause point. This means bdrv_drained_begin() may return with some jobs
using the node still having 'busy == true'.

As a consequence, block_job_detach_aio_context() may get into a
deadlock, waiting for the job to be actually paused, while the coroutine
servicing the job is yielding and doesn't get the opportunity to get
scheduled again. This situation can be reproduced by issuing a
'block-commit' immediately followed by a 'device_del'.

To ensure bdrv_drained_begin() only returns when the jobs have been
paused, we change mirror_drained_poll() to only confirm it's quiesced
when job->paused == true and there aren't any in-flight requests, except
if we reached that point by a drained section initiated by the
mirror/commit job itself.

The other block jobs shouldn't need any changes, as the default
drained_poll() behavior is to only confirm it's quiesced if the job is
not busy or completed.

Signed-off-by: Sergio Lopez <slp@redhat.com>
---
 block/mirror.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/block/mirror.c b/block/mirror.c
index 726d3c27fb..b7f076f97c 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -80,6 +80,7 @@ typedef struct MirrorBlockJob {
     bool initial_zeroing_ongoing;
     int in_active_write_counter;
     bool prepared;
+    bool in_drain;
 } MirrorBlockJob;
 
 typedef struct MirrorBDSOpaque {
@@ -679,9 +680,11 @@ static int mirror_exit_common(Job *job)
 
         /* The mirror job has no requests in flight any more, but we need to
          * drain potential other users of the BDS before changing the graph. */
+        s->in_drain = true;
         bdrv_drained_begin(target_bs);
         bdrv_replace_node(to_replace, target_bs, &local_err);
         bdrv_drained_end(target_bs);
+        s->in_drain = false;
         if (local_err) {
             error_report_err(local_err);
             ret = -EPERM;
@@ -717,6 +720,7 @@ static int mirror_exit_common(Job *job)
     bs_opaque->job = NULL;
 
     bdrv_drained_end(src);
+    s->in_drain = false;
     bdrv_unref(mirror_top_bs);
     bdrv_unref(src);
 
@@ -1000,10 +1004,12 @@ static int coroutine_fn mirror_run(Job *job, Error **errp)
              */
             trace_mirror_before_drain(s, cnt);
 
+            s->in_drain = true;
             bdrv_drained_begin(bs);
             cnt = bdrv_get_dirty_count(s->dirty_bitmap);
             if (cnt > 0 || mirror_flush(s) < 0) {
                 bdrv_drained_end(bs);
+                s->in_drain = false;
                 continue;
             }
 
@@ -1051,6 +1057,7 @@ immediate_exit:
     bdrv_dirty_iter_free(s->dbi);
 
     if (need_drain) {
+        s->in_drain = true;
         bdrv_drained_begin(bs);
     }
 
@@ -1119,6 +1126,16 @@ static void coroutine_fn mirror_pause(Job *job)
 static bool mirror_drained_poll(BlockJob *job)
 {
     MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
+
+    /* If the job isn't paused nor cancelled, we can't be sure that it won't
+     * issue more requets. We make an exception if we've reached this point
+     * from one of our own drain sections, to avoid a deadlock waiting for
+     * ourselves.
+     */
+    if (!s->common.job.paused && !s->common.job.cancelled && !s->in_drain) {
+        return true;
+    }
+
     return !!s->in_flight;
 }
 
-- 
2.20.1

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

* Re: [Qemu-devel] [PATCH] mirror: Confirm we're quiesced only if the job is paused or cancelled
  2019-03-07 14:03 [Qemu-devel] [PATCH] mirror: Confirm we're quiesced only if the job is paused or cancelled Sergio Lopez
@ 2019-03-07 17:15 ` Eric Blake
  0 siblings, 0 replies; 2+ messages in thread
From: Eric Blake @ 2019-03-07 17:15 UTC (permalink / raw)
  To: Sergio Lopez, qemu-block; +Cc: kwolf, qemu-devel, stefanha, mreitz

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

On 3/7/19 8:03 AM, Sergio Lopez wrote:
> While child_job_drained_begin() calls to job_pause(), the job doesn't
> actually transition between states until it runs again and reaches a
> pause point. This means bdrv_drained_begin() may return with some jobs
> using the node still having 'busy == true'.
> 
> As a consequence, block_job_detach_aio_context() may get into a
> deadlock, waiting for the job to be actually paused, while the coroutine
> servicing the job is yielding and doesn't get the opportunity to get
> scheduled again. This situation can be reproduced by issuing a
> 'block-commit' immediately followed by a 'device_del'.
> 
> To ensure bdrv_drained_begin() only returns when the jobs have been
> paused, we change mirror_drained_poll() to only confirm it's quiesced
> when job->paused == true and there aren't any in-flight requests, except
> if we reached that point by a drained section initiated by the
> mirror/commit job itself.
> 
> The other block jobs shouldn't need any changes, as the default
> drained_poll() behavior is to only confirm it's quiesced if the job is
> not busy or completed.
> 
> Signed-off-by: Sergio Lopez <slp@redhat.com>
> ---
>  block/mirror.c | 17 +++++++++++++++++
>  1 file changed, 17 insertions(+)
> 

> @@ -1119,6 +1126,16 @@ static void coroutine_fn mirror_pause(Job *job)
>  static bool mirror_drained_poll(BlockJob *job)
>  {
>      MirrorBlockJob *s = container_of(job, MirrorBlockJob, common);
> +
> +    /* If the job isn't paused nor cancelled, we can't be sure that it won't
> +     * issue more requets. We make an exception if we've reached this point

requests

> +     * from one of our own drain sections, to avoid a deadlock waiting for
> +     * ourselves.
> +     */
> +    if (!s->common.job.paused && !s->common.job.cancelled && !s->in_drain) {
> +        return true;
> +    }
> +
>      return !!s->in_flight;
>  }
>  
> 

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3226
Virtualization:  qemu.org | libvirt.org


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

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

end of thread, other threads:[~2019-03-07 17:16 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-03-07 14:03 [Qemu-devel] [PATCH] mirror: Confirm we're quiesced only if the job is paused or cancelled Sergio Lopez
2019-03-07 17:15 ` Eric Blake

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.