All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] migration/block-dirty-bitmap: don't hold the load lock across stream reads
@ 2026-11-10 18:24 Denis V. Lunev
  2026-11-11 16:14 ` Eric Blake
  0 siblings, 1 reply; 2+ messages in thread
From: Denis V. Lunev @ 2026-11-10 18:24 UTC (permalink / raw)
  To: qemu-devel
  Cc: den, Vladimir Sementsov-Ogievskiy, Eric Blake, John Snow,
	Peter Xu, Fabiano Rosas, qemu-block

From: Denis V. Lunev <den@openvz.org>

dirty_bitmap_load() takes dbm_state.load.lock for each chunk and holds
it while it reads the chunk from the migration stream.

In precopy the load runs in the incoming migration coroutine, which
yields in qemu_fill_buffer() while it waits for data, still holding
the lock. A quit on the destination at that point never completes:
migration_shutdown() calls dirty_bitmap_mig_cancel_incoming(), which
takes the same lock in the same main thread.

In postcopy the load runs in the listen thread, and the main thread
takes the lock in dirty_bitmap_mig_before_vm_start(), called from
loadvm_postcopy_handle_run_bh(), and in migration_shutdown(). The main
loop then waits until the source sends the next chunk.

Read each chunk into a DBMLoadChunk first and take the lock only to
resolve the names and apply the chunk. The lock protects the bitmaps
list and the resolved node and bitmap, which the main thread uses. The
flags and alias strings read from the stream are only touched by the
code doing the load.

Signed-off-by: Denis V. Lunev <den@openvz.org>
Cc: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Cc: Eric Blake <eblake@redhat.com>
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Xu <peterx@redhat.com>
Cc: Fabiano Rosas <farosas@suse.de>
Cc: qemu-block@nongnu.org
---
 migration/block-dirty-bitmap.c | 222 +++++++++++++++++++--------------
 1 file changed, 130 insertions(+), 92 deletions(-)

diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c
index 35623379eb..14e5a984e4 100644
--- a/migration/block-dirty-bitmap.c
+++ b/migration/block-dirty-bitmap.c
@@ -796,12 +796,22 @@ static void dirty_bitmap_state_pending(void *opaque, MigPendingData *data,
     data->postcopy_bytes += pending;
 }
 
+/* Chunk payload, read from the stream without holding DBMLoadState.lock */
+typedef struct DBMLoadChunk {
+    uint32_t granularity;
+    uint8_t start_flags;
+    uint64_t first_byte;
+    uint64_t nr_bytes;
+    uint64_t buf_size;
+    uint8_t *buf;
+} DBMLoadChunk;
+
 /* First occurrence of this bitmap. It should be created if doesn't exist */
-static int dirty_bitmap_load_start(QEMUFile *f, DBMLoadState *s)
+static int dirty_bitmap_load_start(DBMLoadState *s, DBMLoadChunk *c)
 {
     Error *local_err = NULL;
-    uint32_t granularity = qemu_get_be32(f);
-    uint8_t flags = qemu_get_byte(f);
+    uint32_t granularity = c->granularity;
+    uint8_t flags = c->start_flags;
     LoadBitmapState *b;
     bool persistent;
 
@@ -959,7 +969,7 @@ void dirty_bitmap_mig_cancel_incoming(void)
     qemu_mutex_unlock(&s->lock);
 }
 
-static void dirty_bitmap_load_complete(QEMUFile *f, DBMLoadState *s)
+static void dirty_bitmap_load_complete(DBMLoadState *s)
 {
     GSList *item;
     trace_dirty_bitmap_load_complete();
@@ -990,71 +1000,43 @@ static void dirty_bitmap_load_complete(QEMUFile *f, DBMLoadState *s)
     }
 }
 
-static int dirty_bitmap_load_bits(QEMUFile *f, DBMLoadState *s)
+static int dirty_bitmap_load_bits(DBMLoadState *s, DBMLoadChunk *c)
 {
-    uint64_t first_byte = qemu_get_be64(f) << BDRV_SECTOR_BITS;
-    uint64_t nr_bytes = (uint64_t)qemu_get_be32(f) << BDRV_SECTOR_BITS;
-    trace_dirty_bitmap_load_bits_enter(first_byte >> BDRV_SECTOR_BITS,
-                                       nr_bytes >> BDRV_SECTOR_BITS);
+    uint64_t needed_size;
+
+    trace_dirty_bitmap_load_bits_enter(c->first_byte >> BDRV_SECTOR_BITS,
+                                       c->nr_bytes >> BDRV_SECTOR_BITS);
 
     if (s->flags & DIRTY_BITMAP_MIG_FLAG_ZEROES) {
         trace_dirty_bitmap_load_bits_zeroes();
         if (!s->cancelled) {
-            bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, first_byte,
-                                                 nr_bytes, false);
-        }
-    } else {
-        size_t ret;
-        g_autofree uint8_t *buf = NULL;
-        uint64_t buf_size = qemu_get_be64(f);
-        uint64_t needed_size;
-
-        /*
-         * The actual check for buf_size is done a bit later. We can't do it in
-         * cancelled mode as we don't have the bitmap to check the constraints
-         * (so, we allocate a buffer and read prior to the check). On the other
-         * hand, we shouldn't blindly g_malloc the number from the stream.
-         * Actually one chunk should not be larger than CHUNK_SIZE. Let's allow
-         * a bit larger (which means that bitmap migration will fail anyway and
-         * the whole migration will most probably fail soon due to broken
-         * stream).
-         */
-        if (buf_size > 10 * CHUNK_SIZE) {
-            error_report("Bitmap migration stream buffer allocation request "
-                         "is too large");
-            return -EIO;
-        }
-
-        buf = g_malloc(buf_size);
-        ret = qemu_get_buffer(f, buf, buf_size);
-        if (ret != buf_size) {
-            error_report("Failed to read bitmap bits");
-            return -EIO;
-        }
-
-        if (s->cancelled) {
-            return 0;
+            bdrv_dirty_bitmap_deserialize_zeroes(s->bitmap, c->first_byte,
+                                                 c->nr_bytes, false);
         }
+        return 0;
+    }
 
-        needed_size = bdrv_dirty_bitmap_serialization_size(s->bitmap,
-                                                           first_byte,
-                                                           nr_bytes);
-
-        if (needed_size > buf_size ||
-            buf_size > QEMU_ALIGN_UP(needed_size, 4 * sizeof(long))
-             /* Here used same alignment as in send_bitmap_bits */
-        ) {
-            error_report("Migrated bitmap granularity doesn't "
-                         "match the destination bitmap '%s' granularity",
-                         bdrv_dirty_bitmap_name(s->bitmap));
-            cancel_incoming_locked(s);
-            return 0;
-        }
+    if (s->cancelled) {
+        return 0;
+    }
 
-        bdrv_dirty_bitmap_deserialize_part(s->bitmap, buf, first_byte, nr_bytes,
-                                           false);
+    needed_size = bdrv_dirty_bitmap_serialization_size(s->bitmap,
+                                                       c->first_byte,
+                                                       c->nr_bytes);
+
+    if (needed_size > c->buf_size ||
+        c->buf_size > QEMU_ALIGN_UP(needed_size, 4 * sizeof(long))
+         /* Here used same alignment as in send_bitmap_bits */
+    ) {
+        error_report("Migrated bitmap granularity doesn't "
+                     "match the destination bitmap '%s' granularity",
+                     bdrv_dirty_bitmap_name(s->bitmap));
+        cancel_incoming_locked(s);
+        return 0;
     }
 
+    bdrv_dirty_bitmap_deserialize_part(s->bitmap, c->buf, c->first_byte,
+                                       c->nr_bytes, false);
     return 0;
 }
 
@@ -1105,23 +1087,73 @@ static BdrvDirtyBitmap *dirty_bitmap_load_find_bitmap(DBMLoadState *s)
     return NULL;
 }
 
-static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s,
-                                    GHashTable *alias_map)
+static int dirty_bitmap_read_chunk(QEMUFile *f, DBMLoadState *s,
+                                   DBMLoadChunk *c)
+{
+    s->flags = qemu_get_bitmap_flags(f);
+    trace_dirty_bitmap_load_header(s->flags);
+
+    if ((s->flags & DIRTY_BITMAP_MIG_FLAG_DEVICE_NAME) &&
+        !qemu_get_counted_string(f, s->node_alias)) {
+        error_report("Unable to read node alias string");
+        return -EINVAL;
+    }
+
+    if ((s->flags & DIRTY_BITMAP_MIG_FLAG_BITMAP_NAME) &&
+        !qemu_get_counted_string(f, s->bitmap_alias)) {
+        error_report("Unable to read bitmap alias string");
+        return -EINVAL;
+    }
+
+    if (s->flags & DIRTY_BITMAP_MIG_FLAG_START) {
+        c->granularity = qemu_get_be32(f);
+        c->start_flags = qemu_get_byte(f);
+    } else if (s->flags & DIRTY_BITMAP_MIG_FLAG_COMPLETE) {
+        /* no payload */
+    } else if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITS) {
+        c->first_byte = qemu_get_be64(f) << BDRV_SECTOR_BITS;
+        c->nr_bytes = (uint64_t)qemu_get_be32(f) << BDRV_SECTOR_BITS;
+
+        if (!(s->flags & DIRTY_BITMAP_MIG_FLAG_ZEROES)) {
+            c->buf_size = qemu_get_be64(f);
+
+            /*
+             * The actual check for buf_size is done when the chunk is
+             * applied. We can't do it in cancelled mode as we don't have the
+             * bitmap to check the constraints (so, we allocate a buffer and
+             * read prior to the check). On the other hand, we shouldn't
+             * blindly g_malloc the number from the stream. Actually one chunk
+             * should not be larger than CHUNK_SIZE. Let's allow a bit larger
+             * (which means that bitmap migration will fail anyway and the
+             * whole migration will most probably fail soon due to broken
+             * stream).
+             */
+            if (c->buf_size > 10 * CHUNK_SIZE) {
+                error_report("Bitmap migration stream buffer allocation "
+                             "request is too large");
+                return -EIO;
+            }
+
+            c->buf = g_malloc(c->buf_size);
+            if (qemu_get_buffer(f, c->buf, c->buf_size) != c->buf_size) {
+                error_report("Failed to read bitmap bits");
+                return -EIO;
+            }
+        }
+    }
+
+    return qemu_file_get_error(f);
+}
+
+static void dirty_bitmap_load_header(DBMLoadState *s, GHashTable *alias_map)
 {
     GHashTable *bitmap_alias_map = NULL;
     Error *local_err = NULL;
     bool nothing;
-    s->flags = qemu_get_bitmap_flags(f);
-    trace_dirty_bitmap_load_header(s->flags);
 
     nothing = s->flags == (s->flags & DIRTY_BITMAP_MIG_FLAG_EOS);
 
     if (s->flags & DIRTY_BITMAP_MIG_FLAG_DEVICE_NAME) {
-        if (!qemu_get_counted_string(f, s->node_alias)) {
-            error_report("Unable to read node alias string");
-            return -EINVAL;
-        }
-
         if (!s->cancelled) {
             if (alias_map) {
                 const AliasMapInnerNode *amin;
@@ -1165,11 +1197,6 @@ static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s,
     if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITMAP_NAME) {
         const char *bitmap_name;
 
-        if (!qemu_get_counted_string(f, s->bitmap_alias)) {
-            error_report("Unable to read bitmap alias string");
-            return -EINVAL;
-        }
-
         bitmap_name = s->bitmap_alias;
         if (!s->cancelled && bitmap_alias_map) {
             BitmapMigrationBitmapAlias *bmap_inner;
@@ -1206,7 +1233,23 @@ static int dirty_bitmap_load_header(QEMUFile *f, DBMLoadState *s,
         error_report("Error: block device name is not set");
         cancel_incoming_locked(s);
     }
+}
 
+static int dirty_bitmap_apply_chunk(DBMLoadState *s, DBMLoadChunk *c,
+                                    GHashTable *alias_map)
+{
+    dirty_bitmap_load_header(s, alias_map);
+
+    if (s->flags & DIRTY_BITMAP_MIG_FLAG_START) {
+        return dirty_bitmap_load_start(s, c);
+    }
+    if (s->flags & DIRTY_BITMAP_MIG_FLAG_COMPLETE) {
+        dirty_bitmap_load_complete(s);
+        return 0;
+    }
+    if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITS) {
+        return dirty_bitmap_load_bits(s, c);
+    }
     return 0;
 }
 
@@ -1240,28 +1283,23 @@ static int dirty_bitmap_load(QEMUFile *f, void *opaque, int version_id)
     }
 
     do {
-        QEMU_LOCK_GUARD(&s->lock);
-
-        ret = dirty_bitmap_load_header(f, s, alias_map);
-        if (ret < 0) {
-            cancel_incoming_locked(s);
-            goto fail;
-        }
-
-        if (s->flags & DIRTY_BITMAP_MIG_FLAG_START) {
-            ret = dirty_bitmap_load_start(f, s);
-        } else if (s->flags & DIRTY_BITMAP_MIG_FLAG_COMPLETE) {
-            dirty_bitmap_load_complete(f, s);
-        } else if (s->flags & DIRTY_BITMAP_MIG_FLAG_BITS) {
-            ret = dirty_bitmap_load_bits(f, s);
-        }
+        DBMLoadChunk c = {};
 
-        if (!ret) {
-            ret = qemu_file_get_error(f);
+        /*
+         * dirty_bitmap_mig_before_vm_start() takes the lock from the main
+         * thread, so never hold it while waiting for the stream.
+         */
+        ret = dirty_bitmap_read_chunk(f, s, &c);
+        WITH_QEMU_LOCK_GUARD(&s->lock) {
+            if (!ret) {
+                ret = dirty_bitmap_apply_chunk(s, &c, alias_map);
+            }
+            if (ret) {
+                cancel_incoming_locked(s);
+            }
         }
-
+        g_free(c.buf);
         if (ret) {
-            cancel_incoming_locked(s);
             goto fail;
         }
     } while (!(s->flags & DIRTY_BITMAP_MIG_FLAG_EOS));

base-commit: 723622bedb0b423de7d1c61ebbe6e7a71934949c
-- 
2.53.0



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

end of thread, other threads:[~2026-11-11 16:15 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-10 18:24 [PATCH] migration/block-dirty-bitmap: don't hold the load lock across stream reads Denis V. Lunev
2026-11-11 16:14 ` 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.