All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 00/11] migration: fast snapshot load
@ 2026-08-01  2:36 Aadeshveer Singh
  2026-08-01  2:36 ` [PATCH v4 01/11] migration: Propagate error in postcopy setup functions Aadeshveer Singh
                   ` (11 more replies)
  0 siblings, 12 replies; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

This series implements a "fast snapshot load" mechanism to
significantly reduce the perceived resume time of a VM from a snapshot
file.

Currently, resuming a VM from a snapshot file requires loading all RAM
pages into the QEMU instance before execution begins. This extension
allows the user to run the VM nearly instantly by loading only the
required device states up front and loading RAM pages lazily, by
trapping access to pages that have not yet been loaded.

Using the Linux userfaultfd syscall, a fault thread catches all page
faults caused by the guest and loads in the pages required to keep
the VM running. Concurrently, an eager background thread iteratively
loads all remaining pages into RAM so the guest does not have to
depend on the fault thread indefinitely.

Much of code is reused from postcopy for fault handling and precopy
for reading mapped ram file. Implementation revolves around two
threads named the fault thread and eager load thread. Fault thread as
name suggests catches page faults by the guest and serves them using
userfaultfd. Postcopy fault thread is reused but instead of requesting
source for a page it loads the page directly by reading from file. In
order to remove the dependency of guest on fault thread indefinitely
the eager load thread loads in the entire RAM sequentially, and after
iterating through the entire RAM signals fault thread to exit and
calls cleanup.

In order to prevent the case of a page being loaded twice(in the
case when eager load thread is loading it and fault thread also
tries to serve fault on same page) a bitmap called pending_bmap is
used to track pages which are pending and not being loaded by any
thread. Atomic operations on this bitmap allows coordination between
threads to prevent any unwanted behaviours

This patch series was tested on a single machine:
host OS   : Fedora 44
host RAM  : 32GB DDR5(4KB pagesize)
host CPU  : Intel Ultra9 185H(22 cores, x86_64)

Guests tested:
- KVM enabled
  guest OS  : Fedora 44
  guest RAM : 16GB(4KB pagesize)
  guest CPU : 4 cores(x86_64)
- emulated power PC
  guest OS  : Debian 10
  guest RAM : 2GB(64KB pagesize)
  guest CPU : 2 cores(ppc)
- host using 2MB hugepages
  guest OS  : Debian 13
  guest RAM : 16GB(16KB pagesize)
  guest CPU : 4 cores(x86_64)

Future direction:
- Add support for multifd
- Add support for vhost-user

---
v3 -> v4

- Set errp in qemu_get_buffer_at on failing if file is in failure
  state to ensure errp is set if a function returns failure
- Update logic for postcopy_mapped_ram_load_page in patch 7 to support
  all possibilities of guest and host pagesizes as pointed by Peter
- This also improves on correctness by not relying on mapped ram
  filling zeropages with zero values as pointed by Peter
- Simplified postcopy_ram_eager_load_thread logic in patch 8 for exit
  as suggested by Peter
- Use a more generalized language in documentation overview as
  suggested by Peter
- Added a check against vhost user in case of fast snapshot load in
  options.c(Patch 9) as suggested by Peter

Aadeshveer Singh (11):
  migration: Propagate error in postcopy setup functions
  migration: Extract blocktime marking helper
  migration: Rename postcopy_listen_thread_bh
  migration: Use file_bmap for RAMBlock during incoming file load
  migration: Make qemu_get_buffer_at() thread-safe
  migration: add RAMBlock field and helper for fast snapshot load
  migration: add support for fault thread to load pages from disk
  migration: add eager load thread and setup for fast snapshot load
  migration: update capability conflict test for postcopy-ram+mapped-ram
  migration/tests: Add test for fast snapshot load
  docs/migration: Add documentation for fast snapshot load feature

 docs/devel/migration/fast-snapshot-load.rst |  82 +++++
 docs/devel/migration/features.rst           |   1 +
 include/qemu/notify.h                       |   2 +
 include/system/ramblock.h                   |   6 +
 migration/migration.c                       |  60 ++--
 migration/migration.h                       |   5 +
 migration/options.c                         |  20 +-
 migration/postcopy-ram.c                    | 324 +++++++++++++++++---
 migration/postcopy-ram.h                    |   8 +-
 migration/qemu-file.c                       |  11 +-
 migration/qemu-file.h                       |   4 +-
 migration/ram.c                             |  94 +++++-
 migration/savevm.c                          |  16 +
 migration/savevm.h                          |   2 +
 migration/trace-events                      |   2 +
 tests/qtest/migration/file-tests.c          |  24 ++
 tests/qtest/migration/misc-tests.c          |  52 ----
 util/notify.c                               |   5 +
 18 files changed, 575 insertions(+), 143 deletions(-)
 create mode 100644 docs/devel/migration/fast-snapshot-load.rst

-- 
2.55.0



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

* [PATCH v4 01/11] migration: Propagate error in postcopy setup functions
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
@ 2026-08-01  2:36 ` Aadeshveer Singh
  2026-08-10 15:08   ` Juraj Marcin
  2026-08-01  2:36 ` [PATCH v4 02/11] migration: Extract blocktime marking helper Aadeshveer Singh
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

Modernize error handling in postcopy_ram_incoming_setup() and
postcopy_temp_pages_setup() by replacing error_reports and local error
handling with standard Error propagation.

Replace use of strerror() on errno with error_setg_errno() for modular
handling of errors and change return values to -1 on failure as no
caller checks the actual return value.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
---
 migration/postcopy-ram.c | 41 +++++++++++++++++-----------------------
 migration/postcopy-ram.h |  2 +-
 2 files changed, 18 insertions(+), 25 deletions(-)

diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
index f5ef93f193..96a65aa976 100644
--- a/migration/postcopy-ram.c
+++ b/migration/postcopy-ram.c
@@ -1464,10 +1464,9 @@ retry:
     return NULL;
 }
 
-static int postcopy_temp_pages_setup(MigrationIncomingState *mis)
+static int postcopy_temp_pages_setup(MigrationIncomingState *mis, Error **errp)
 {
     PostcopyTmpPage *tmp_page;
-    int err;
     unsigned i, channels;
     void *temp_page;
 
@@ -1487,11 +1486,11 @@ static int postcopy_temp_pages_setup(MigrationIncomingState *mis)
         temp_page = mmap(NULL, mis->largest_page_size, PROT_READ | PROT_WRITE,
                          MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
         if (temp_page == MAP_FAILED) {
-            err = errno;
-            error_report("%s: Failed to map postcopy_tmp_pages[%d]: %s",
-                         __func__, i, strerror(err));
+            error_setg_errno(errp, errno,
+                             "%s: Failed to map postcopy_tmp_pages[%d]",
+                             __func__, i);
             /* Clean up will be done later */
-            return -err;
+            return -1;
         }
         tmp_page->tmp_huge_page = temp_page;
         /* Initialize default states for each tmp page */
@@ -1505,11 +1504,10 @@ static int postcopy_temp_pages_setup(MigrationIncomingState *mis)
                                        PROT_READ | PROT_WRITE,
                                        MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
     if (mis->postcopy_tmp_zero_page == MAP_FAILED) {
-        err = errno;
         mis->postcopy_tmp_zero_page = NULL;
-        error_report("%s: Failed to map large zero page %s",
-                     __func__, strerror(err));
-        return -err;
+        error_setg_errno(errp, errno, "%s: Failed to map large zero page",
+                         __func__);
+        return -1;
     }
 
     memset(mis->postcopy_tmp_zero_page, '\0', mis->largest_page_size);
@@ -1517,15 +1515,13 @@ static int postcopy_temp_pages_setup(MigrationIncomingState *mis)
     return 0;
 }
 
-int postcopy_ram_incoming_setup(MigrationIncomingState *mis)
+int postcopy_ram_incoming_setup(MigrationIncomingState *mis, Error **errp)
 {
-    Error *local_err = NULL;
-
     /* Open the fd for the kernel to give us userfaults */
     mis->userfault_fd = uffd_open(O_CLOEXEC | O_NONBLOCK);
     if (mis->userfault_fd == -1) {
-        error_report("%s: Failed to open userfault fd: %s", __func__,
-                     strerror(errno));
+        error_setg_errno(errp, errno, "%s: Failed to open userfault fd",
+                         __func__);
         return -1;
     }
 
@@ -1533,8 +1529,7 @@ int postcopy_ram_incoming_setup(MigrationIncomingState *mis)
      * Although the host check already tested the API, we need to
      * do the check again as an ABI handshake on the new fd.
      */
-    if (!ufd_check_and_apply(mis->userfault_fd, mis, &local_err)) {
-        error_report_err(local_err);
+    if (!ufd_check_and_apply(mis->userfault_fd, mis, errp)) {
         return -1;
     }
 
@@ -1546,8 +1541,8 @@ int postcopy_ram_incoming_setup(MigrationIncomingState *mis)
     /* Now an eventfd we use to tell the fault-thread to quit */
     mis->userfault_event_fd = eventfd(0, EFD_CLOEXEC);
     if (mis->userfault_event_fd == -1) {
-        error_report("%s: Opening userfault_event_fd: %s", __func__,
-                     strerror(errno));
+        error_setg_errno(errp, errno, "%s: Opening userfault_event_fd",
+                         __func__);
         close(mis->userfault_fd);
         return -1;
     }
@@ -1559,12 +1554,11 @@ int postcopy_ram_incoming_setup(MigrationIncomingState *mis)
 
     /* Mark so that we get notified of accesses to unwritten areas */
     if (foreach_not_ignored_block(ram_block_enable_notify, mis)) {
-        error_report("ram_block_enable_notify failed");
+        error_setg(errp, "ram_block_enable_notify failed");
         return -1;
     }
 
-    if (postcopy_temp_pages_setup(mis)) {
-        /* Error dumped in the sub-function */
+    if (postcopy_temp_pages_setup(mis, errp)) {
         return -1;
     }
 
@@ -2201,9 +2195,8 @@ int postcopy_incoming_setup(MigrationIncomingState *mis, Error **errp)
      * shouldn't be doing anything yet so don't actually expect requests
      */
     if (migrate_postcopy_ram()) {
-        if (postcopy_ram_incoming_setup(mis)) {
+        if (postcopy_ram_incoming_setup(mis, errp)) {
             postcopy_ram_incoming_cleanup(mis);
-            error_setg(errp, "Failed to setup incoming postcopy RAM blocks");
             return -1;
         }
     }
diff --git a/migration/postcopy-ram.h b/migration/postcopy-ram.h
index a080dd65a7..98d918713f 100644
--- a/migration/postcopy-ram.h
+++ b/migration/postcopy-ram.h
@@ -23,7 +23,7 @@ bool postcopy_ram_supported_by_host(MigrationIncomingState *mis,
  * Make all of RAM sensitive to accesses to areas that haven't yet been written
  * and wire up anything necessary to deal with it.
  */
-int postcopy_ram_incoming_setup(MigrationIncomingState *mis);
+int postcopy_ram_incoming_setup(MigrationIncomingState *mis, Error **errp);
 
 /*
  * Initialise postcopy-ram, setting the RAM to a state where we can go into
-- 
2.55.0



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

* [PATCH v4 02/11] migration: Extract blocktime marking helper
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
  2026-08-01  2:36 ` [PATCH v4 01/11] migration: Propagate error in postcopy setup functions Aadeshveer Singh
@ 2026-08-01  2:36 ` Aadeshveer Singh
  2026-08-10 15:08   ` Juraj Marcin
  2026-08-01  2:36 ` [PATCH v4 03/11] migration: Rename postcopy_listen_thread_bh Aadeshveer Singh
                   ` (9 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

Use new function try_mark_postcopy_blocktim_begin() to call
mark_postcopy_blocktime_begin if page was not received and return if it
actually marked it.

This will help in having a cleaner API to call
mark_postcopy_blocktime_begin as it requires the page to not be received
by asserting on it's existance in RAMBlock->receivedmap.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
---
 migration/migration.c    | 24 ++----------------------
 migration/postcopy-ram.c | 31 +++++++++++++++++++++++++++++++
 migration/postcopy-ram.h |  3 +++
 3 files changed, 36 insertions(+), 22 deletions(-)

diff --git a/migration/migration.c b/migration/migration.c
index 074d3f2c69..07394d8eea 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -577,32 +577,12 @@ int migrate_send_rp_req_pages(MigrationIncomingState *mis,
                               RAMBlock *rb, ram_addr_t start, uint64_t haddr,
                               uint32_t tid)
 {
-    void *aligned = (void *)(uintptr_t)ROUND_DOWN(haddr, qemu_ram_pagesize(rb));
-    bool received = false;
-
-    WITH_QEMU_LOCK_GUARD(&mis->page_request_mutex) {
-        received = ramblock_recv_bitmap_test_byte_offset(rb, start);
-        if (!received) {
-            if (!g_tree_lookup(mis->page_requested, aligned)) {
-                /*
-                 * The page has not been received, and it's not yet in the
-                 * page request list.  Queue it.  Set the value of element
-                 * to 1, so that things like g_tree_lookup() will return
-                 * TRUE (1) when found.
-                 */
-                g_tree_insert(mis->page_requested, aligned, (gpointer)1);
-                qatomic_inc(&mis->page_requested_count);
-                trace_postcopy_page_req_add(aligned, mis->page_requested_count);
-            }
-            mark_postcopy_blocktime_begin(haddr, tid, rb);
-        }
-    }
-
     /*
+     * If not able to mark page for blocktime it must already be present.
      * If the page is there, skip sending the message.  We don't even need the
      * lock because as long as the page arrived, it'll be there forever.
      */
-    if (received) {
+    if (!try_mark_postcopy_blocktime_begin(mis, rb, start, haddr, tid)) {
         return 0;
     }
 
diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
index 96a65aa976..4b8f882e1e 100644
--- a/migration/postcopy-ram.c
+++ b/migration/postcopy-ram.c
@@ -1057,6 +1057,37 @@ static void blocktime_fault_inject(PostcopyBlocktimeContext *ctx,
     trace_postcopy_blocktime_begin(addr, time, cpu, !!head);
 }
 
+/*
+ * Take @page_request_mutex and try marking postcopy blocktime begin.
+ * Return true if marking is successful and false if page alredy exists.
+ */
+bool try_mark_postcopy_blocktime_begin(MigrationIncomingState *mis,
+                                       RAMBlock *rb, ram_addr_t start,
+                                       uint64_t haddr, uint32_t tid)
+{
+    bool received = false;
+    void *aligned = (void *)(uintptr_t)ROUND_DOWN(haddr, qemu_ram_pagesize(rb));
+
+    WITH_QEMU_LOCK_GUARD(&mis->page_request_mutex) {
+        received = ramblock_recv_bitmap_test_byte_offset(rb, start);
+        if (!received) {
+            if (!g_tree_lookup(mis->page_requested, aligned)) {
+                /*
+                 * The page has not been received, and it's not yet in the
+                 * page request list.  Queue it.  Set the value of element
+                 * to 1, so that things like g_tree_lookup() will return
+                 * TRUE (1) when found.
+                 */
+                g_tree_insert(mis->page_requested, aligned, (gpointer)1);
+                qatomic_inc(&mis->page_requested_count);
+                trace_postcopy_page_req_add(aligned, mis->page_requested_count);
+            }
+            mark_postcopy_blocktime_begin((uint64_t)aligned, tid, rb);
+        }
+    }
+    return !received;
+}
+
 /*
  * This function is being called when pagefault occurs. It tracks down vCPU
  * blocking time.  It's protected by @page_request_mutex.
diff --git a/migration/postcopy-ram.h b/migration/postcopy-ram.h
index 98d918713f..ea879f0bc6 100644
--- a/migration/postcopy-ram.h
+++ b/migration/postcopy-ram.h
@@ -196,6 +196,9 @@ void postcopy_preempt_new_channel(MigrationIncomingState *mis, QEMUFile *file);
 void postcopy_preempt_setup(MigrationState *s);
 int postcopy_preempt_establish_channel(MigrationState *s);
 bool postcopy_is_paused(MigrationStatus status);
+bool try_mark_postcopy_blocktime_begin(MigrationIncomingState *mis,
+                                       RAMBlock *rb, ram_addr_t start,
+                                       uint64_t haddr, uint32_t tid);
 void mark_postcopy_blocktime_begin(uintptr_t addr, uint32_t ptid,
                                    RAMBlock *rb);
 
-- 
2.55.0



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

* [PATCH v4 03/11] migration: Rename postcopy_listen_thread_bh
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
  2026-08-01  2:36 ` [PATCH v4 01/11] migration: Propagate error in postcopy setup functions Aadeshveer Singh
  2026-08-01  2:36 ` [PATCH v4 02/11] migration: Extract blocktime marking helper Aadeshveer Singh
@ 2026-08-01  2:36 ` Aadeshveer Singh
  2026-08-10 15:09   ` Juraj Marcin
  2026-08-01  2:36 ` [PATCH v4 04/11] migration: Use file_bmap for RAMBlock during incoming file load Aadeshveer Singh
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

Rename postcopy_listen_thread_bh to postcopy_incoming_complete_bh for a
more robust naming scheme. This will allow fast snapshot load to use
this bh funciton directly to cleanup the incoming state.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
---
 migration/postcopy-ram.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
index 4b8f882e1e..2e2c9fae10 100644
--- a/migration/postcopy-ram.c
+++ b/migration/postcopy-ram.c
@@ -2103,7 +2103,7 @@ bool postcopy_is_paused(MigrationStatus status)
         status == MIGRATION_STATUS_POSTCOPY_RECOVER_SETUP;
 }
 
-static void postcopy_listen_thread_bh(void *opaque)
+static void postcopy_incoming_complete_bh(void *opaque)
 {
     MigrationState *s = migrate_get_current();
     MigrationIncomingState *mis = migration_incoming_get_current();
@@ -2211,7 +2211,7 @@ out:
     rcu_unregister_thread();
     postcopy_state_set(POSTCOPY_INCOMING_END);
 
-    migration_bh_schedule(postcopy_listen_thread_bh, NULL);
+    migration_bh_schedule(postcopy_incoming_complete_bh, NULL);
 
     object_unref(OBJECT(migr));
 
-- 
2.55.0



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

* [PATCH v4 04/11] migration: Use file_bmap for RAMBlock during incoming file load
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
                   ` (2 preceding siblings ...)
  2026-08-01  2:36 ` [PATCH v4 03/11] migration: Rename postcopy_listen_thread_bh Aadeshveer Singh
@ 2026-08-01  2:36 ` Aadeshveer Singh
  2026-08-10 15:09   ` Juraj Marcin
  2026-08-01  2:36 ` [PATCH v4 05/11] migration: Make qemu_get_buffer_at() thread-safe Aadeshveer Singh
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

Replace the temporary bitmap with the existing file_bmap attribute of
the RAMBlock. This acts as a preparatory change for the upcoming fast
snapshot load feature.

Reusing this bitmap allows the destination to track page types during
a postcopy load, enabling faster, direct placement of zero pages.

Since file_bmap is currently only utilized during the migration save
phase, it can be safely repurposed during the load phase without
introducing conflicts.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
---
 migration/ram.c | 33 +++++++++++++++++++++++++++------
 1 file changed, 27 insertions(+), 6 deletions(-)

diff --git a/migration/ram.c b/migration/ram.c
index fc38ffbf8a..4728f14d73 100644
--- a/migration/ram.c
+++ b/migration/ram.c
@@ -252,6 +252,17 @@ int foreach_not_ignored_block(RAMBlockIterFunc func, void *opaque)
     return ret;
 }
 
+static void ramblock_file_bmap_init(void)
+{
+    RAMBlock *rb;
+
+    RAMBLOCK_FOREACH_NOT_IGNORED(rb) {
+        assert(!rb->file_bmap);
+        size_t size = rb->max_length >> qemu_target_page_bits();
+        rb->file_bmap = bitmap_new(size);
+    }
+}
+
 static void ramblock_recv_map_init(void)
 {
     RAMBlock *rb;
@@ -3749,6 +3760,9 @@ static int ram_load_setup(QEMUFile *f, void *opaque, Error **errp)
 {
     xbzrle_load_setup();
     ramblock_recv_map_init();
+    if (migrate_mapped_ram()) {
+        ramblock_file_bmap_init();
+    }
 
     return 0;
 }
@@ -3766,8 +3780,8 @@ static int ram_load_cleanup(void *opaque)
     xbzrle_load_cleanup();
 
     RAMBLOCK_FOREACH_NOT_IGNORED(rb) {
-        g_free(rb->receivedmap);
-        rb->receivedmap = NULL;
+        g_clear_pointer(&rb->receivedmap, g_free);
+        g_clear_pointer(&rb->file_bmap, g_free);
     }
 
     return 0;
@@ -4142,11 +4156,18 @@ err:
 static void parse_ramblock_mapped_ram(QEMUFile *f, RAMBlock *block,
                                       ram_addr_t length, Error **errp)
 {
-    g_autofree unsigned long *bitmap = NULL;
     MappedRamHeader header;
     size_t bitmap_size;
     long num_pages;
 
+    if (length > block->max_length) {
+        error_setg(errp,
+                   "mapped-ram header length %" PRIu64 " exceeds "
+                   "RAMBlock(\"%s\") max_length %" PRIu64,
+                   (uint64_t)length, block->idstr, (uint64_t)block->max_length);
+        return;
+    }
+
     if (!mapped_ram_read_header(f, &header, errp)) {
         return;
     }
@@ -4174,14 +4195,14 @@ static void parse_ramblock_mapped_ram(QEMUFile *f, RAMBlock *block,
     num_pages = length / header.page_size;
     bitmap_size = BITS_TO_LONGS(num_pages) * sizeof(unsigned long);
 
-    bitmap = g_malloc0(bitmap_size);
-    if (qemu_get_buffer_at(f, (uint8_t *)bitmap, bitmap_size,
+    if (qemu_get_buffer_at(f, (uint8_t *)block->file_bmap, bitmap_size,
                            header.bitmap_offset) != bitmap_size) {
         error_setg(errp, "Error reading dirty bitmap");
         return;
     }
 
-    if (!read_ramblock_mapped_ram(f, block, num_pages, bitmap, errp)) {
+    if (!read_ramblock_mapped_ram(f, block, num_pages, block->file_bmap,
+                                  errp)) {
         return;
     }
 
-- 
2.55.0



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

* [PATCH v4 05/11] migration: Make qemu_get_buffer_at() thread-safe
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
                   ` (3 preceding siblings ...)
  2026-08-01  2:36 ` [PATCH v4 04/11] migration: Use file_bmap for RAMBlock during incoming file load Aadeshveer Singh
@ 2026-08-01  2:36 ` Aadeshveer Singh
  2026-08-10 14:14   ` Peter Xu
  2026-08-10 15:10   ` Juraj Marcin
  2026-08-01  2:36 ` [PATCH v4 06/11] migration: add RAMBlock field and helper for fast snapshot load Aadeshveer Singh
                   ` (6 subsequent siblings)
  11 siblings, 2 replies; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

Remove the internal QEMUFile error state modification from
qemu_get_buffer_at(). This function is called by two functions, both of
which already check for unexpected return values and handle their own
error reporting.

Removing this shared state modification makes qemu_get_buffer_at()
strictly thread-safe for concurrent disk reads, serving as a
preparatory change for the upcoming fast snapshot load feature.

Removed local error by passing errp to improve on error handling,
consequently also change caller to use error_prevent and not error_setg.

Set errp in case of f->last_error as function should set errp in case it
fails, and caller can simply use error_prepend without checking errp in
case of error.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
---
 migration/qemu-file.c | 11 +++++------
 migration/qemu-file.h |  4 ++--
 migration/ram.c       |  6 +++---
 3 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/migration/qemu-file.c b/migration/qemu-file.c
index d5a48115bd..2feaeb8982 100644
--- a/migration/qemu-file.c
+++ b/migration/qemu-file.c
@@ -550,17 +550,16 @@ void qemu_put_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
 }
 
 
-size_t qemu_get_buffer_at(QEMUFile *f, uint8_t *buf, size_t buflen,
-                          off_t pos)
+size_t qemu_get_buffer_at(QEMUFile *f, uint8_t *buf, size_t buflen, off_t pos,
+                          Error **errp)
 {
-    Error *err = NULL;
-
     if (f->last_error) {
+        error_setg(errp, "Cannot read from file: stream is in error state %d",
+                   f->last_error);
         return 0;
     }
 
-    if (qio_channel_pread_all(f->ioc, buf, buflen, pos, &err) < 0) {
-        qemu_file_set_error_obj(f, -EIO, err);
+    if (qio_channel_pread_all(f->ioc, buf, buflen, pos, errp) < 0) {
         return 0;
     }
 
diff --git a/migration/qemu-file.h b/migration/qemu-file.h
index 8f824c124d..966766788d 100644
--- a/migration/qemu-file.h
+++ b/migration/qemu-file.h
@@ -76,8 +76,8 @@ void qemu_set_offset(QEMUFile *f, off_t off, int whence);
 off_t qemu_get_offset(QEMUFile *f);
 void qemu_put_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
                         off_t pos);
-size_t qemu_get_buffer_at(QEMUFile *f, uint8_t *buf, size_t buflen,
-                          off_t pos);
+size_t qemu_get_buffer_at(QEMUFile *f, uint8_t *buf, size_t buflen, off_t pos,
+                          Error **errp);
 
 QIOChannel *qemu_file_get_ioc(QEMUFile *file);
 int qemu_file_put_fd(QEMUFile *f, int fd);
diff --git a/migration/ram.c b/migration/ram.c
index 4728f14d73..967db7c0db 100644
--- a/migration/ram.c
+++ b/migration/ram.c
@@ -4127,7 +4127,7 @@ static bool read_ramblock_mapped_ram(QEMUFile *f, RAMBlock *block,
                                               block->pages_offset + offset);
             } else {
                 read = qemu_get_buffer_at(f, host, size,
-                                          block->pages_offset + offset);
+                                          block->pages_offset + offset, errp);
             }
 
             if (!read) {
@@ -4196,8 +4196,8 @@ static void parse_ramblock_mapped_ram(QEMUFile *f, RAMBlock *block,
     bitmap_size = BITS_TO_LONGS(num_pages) * sizeof(unsigned long);
 
     if (qemu_get_buffer_at(f, (uint8_t *)block->file_bmap, bitmap_size,
-                           header.bitmap_offset) != bitmap_size) {
-        error_setg(errp, "Error reading dirty bitmap");
+                           header.bitmap_offset, errp) != bitmap_size) {
+        error_prepend(errp, "Error reading dirty bitmap");
         return;
     }
 
-- 
2.55.0



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

* [PATCH v4 06/11] migration: add RAMBlock field and helper for fast snapshot load
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
                   ` (4 preceding siblings ...)
  2026-08-01  2:36 ` [PATCH v4 05/11] migration: Make qemu_get_buffer_at() thread-safe Aadeshveer Singh
@ 2026-08-01  2:36 ` Aadeshveer Singh
  2026-08-10 15:12   ` Juraj Marcin
  2026-08-01  2:36 ` [PATCH v4 07/11] migration: add support for fault thread to load pages from disk Aadeshveer Singh
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

Add pending_bmap field per RAMBlock which is a Bitmap to store
internal state of which pages have been read by some thread to ensure
coordination between fault thread and eager load thread.

Modify parse_ramblock_mapped_ram(), to not load the actual RAMBlocks
data in postcopy case as that will be loaded by fault thread and eager
thread after the VM starts running.

Change ram_load() to use new function ram_should_load_postcopy_pages()
to decide how to load/read RAM.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
---
 include/system/ramblock.h |  6 ++++
 migration/ram.c           | 59 +++++++++++++++++++++++++++++++++++----
 2 files changed, 59 insertions(+), 6 deletions(-)

diff --git a/include/system/ramblock.h b/include/system/ramblock.h
index 4435f8d55f..83187bf44c 100644
--- a/include/system/ramblock.h
+++ b/include/system/ramblock.h
@@ -60,6 +60,12 @@ struct RAMBlock {
 
     /* Bitmap of already received pages.  Only used on destination side. */
     unsigned long *receivedmap;
+    /*
+     * Bitmap for pages that are yet to be read from disk. It is required for
+     * fault thread and eager thread to keep note of which pages are currently
+     * being read. Used by fast snapshot load.
+     */
+    unsigned long *pending_bmap;
 
     /*
      * bitmap to track already cleared dirty bitmap.  When the bit is
diff --git a/migration/ram.c b/migration/ram.c
index 967db7c0db..734fd90e12 100644
--- a/migration/ram.c
+++ b/migration/ram.c
@@ -263,6 +263,18 @@ static void ramblock_file_bmap_init(void)
     }
 }
 
+static void ramblock_pending_bmap_init(void)
+{
+    RAMBlock *rb;
+
+    RAMBLOCK_FOREACH_NOT_IGNORED(rb) {
+        assert(!rb->pending_bmap);
+        size_t size = rb->max_length / qemu_ram_pagesize(rb);
+        rb->pending_bmap = bitmap_new(size);
+        bitmap_set(rb->pending_bmap, 0, size);
+    }
+}
+
 static void ramblock_recv_map_init(void)
 {
     RAMBlock *rb;
@@ -3762,6 +3774,10 @@ static int ram_load_setup(QEMUFile *f, void *opaque, Error **errp)
     ramblock_recv_map_init();
     if (migrate_mapped_ram()) {
         ramblock_file_bmap_init();
+        if (migrate_postcopy_ram()) {
+            /* fast snapshot load */
+            ramblock_pending_bmap_init();
+        }
     }
 
     return 0;
@@ -3782,6 +3798,7 @@ static int ram_load_cleanup(void *opaque)
     RAMBLOCK_FOREACH_NOT_IGNORED(rb) {
         g_clear_pointer(&rb->receivedmap, g_free);
         g_clear_pointer(&rb->file_bmap, g_free);
+        g_clear_pointer(&rb->pending_bmap, g_free);
     }
 
     return 0;
@@ -4201,9 +4218,12 @@ static void parse_ramblock_mapped_ram(QEMUFile *f, RAMBlock *block,
         return;
     }
 
-    if (!read_ramblock_mapped_ram(f, block, num_pages, block->file_bmap,
-                                  errp)) {
-        return;
+    if (!migrate_postcopy_ram()) {
+        /* Do not load RAM during setup for fast snapshot load */
+        if (!read_ramblock_mapped_ram(f, block, num_pages, block->file_bmap,
+                                      errp)) {
+            return;
+        }
     }
 
     /* Skip pages array */
@@ -4475,15 +4495,42 @@ static int ram_load_precopy(QEMUFile *f)
     return ret;
 }
 
+static bool ram_should_load_postcopy_pages(void)
+{
+    /* This is pure precopy, we don't need to load pages in postcopy way */
+    if (!postcopy_is_running()) {
+        return false;
+    }
+
+    /*
+     * This is postcopy, but when with mapped-ram, pages are not loaded in the
+     * migration stream here, but done separately in a thread eagerly reading
+     * pages from the snapshot.  Here, we only need to read the ram headers,
+     * reusing the precopy code.
+     * TODO: when we have separate function to parse RAM headers we should
+     * switch to that.
+     */
+    if (migrate_mapped_ram()) {
+        return false;
+    }
+
+    /*
+     * Genuine network postcopy, we will load pages in this current stream and
+     * they need to be done in postcopy way.
+     */
+    return true;
+}
+
 static int ram_load(QEMUFile *f, void *opaque, int version_id)
 {
     int ret = 0;
     static uint64_t seq_iter;
     /*
      * If system is running in postcopy mode, page inserts to host memory must
-     * be atomic
+     * be atomic. However, fast snapshot load uses the mapped ram precopy like
+     * path to read block headers and populating bitmaps.
      */
-    bool postcopy_running = postcopy_is_running();
+    bool load_postcopy_pages = ram_should_load_postcopy_pages();
 
     seq_iter++;
 
@@ -4499,7 +4546,7 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id)
      */
     trace_ram_load_start();
     WITH_RCU_READ_LOCK_GUARD() {
-        if (postcopy_running) {
+        if (load_postcopy_pages) {
             /*
              * Note!  Here RAM_CHANNEL_PRECOPY is the precopy channel of
              * postcopy migration, we have another RAM_CHANNEL_POSTCOPY to
-- 
2.55.0



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

* [PATCH v4 07/11] migration: add support for fault thread to load pages from disk
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
                   ` (5 preceding siblings ...)
  2026-08-01  2:36 ` [PATCH v4 06/11] migration: add RAMBlock field and helper for fast snapshot load Aadeshveer Singh
@ 2026-08-01  2:36 ` Aadeshveer Singh
  2026-08-08  4:34   ` Aadeshveer Singh
  2026-08-01  2:36 ` [PATCH v4 08/11] migration: add eager load thread and setup for fast snapshot load Aadeshveer Singh
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

In fast snapshot load, we would like to serve faults as soon as possible
hence loading pages directly instead of requesting a source

Add postcopy_mapped_ram_load_page() function which serves single page
fault. It uses bitmap_test_and_clear_atomic() on pending_bmap to prevent
multiple threads from loading same page. It loads a page or pages
depending on size of guest pages and host pages, loading exactly the
larger of two. If the entire page is zero postcopy_place_page_zero() is
used for efficiency and in case some part is non zero it is part by part
loaded on loop using new function postcopy_mapped_ram_load_guest_page()
which loads a single guest page in a buffer which is then placed using
postcopy_place_page(). This covers all possible cases for various page
sizes of host and guest.

Update postcopy_ram_fault_thread to call postcopy_mapped_ram_load_page
instead of requesting source in case of fast snapshot load. to_src_file
check is bypassed in fast snapshot load case as there is no source.

Call try_mark_postcopy_blocktime_begin on every page fault to support
postcopy-blocktime.

Allocate another channel in postcopy_temp_pages_setup(like the preempt
case), for both the fault thread and eager thread to load pages
independently.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
---
 migration/postcopy-ram.c | 174 +++++++++++++++++++++++++++++++++++----
 1 file changed, 157 insertions(+), 17 deletions(-)

diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
index 2e2c9fae10..1da9c4309b 100644
--- a/migration/postcopy-ram.c
+++ b/migration/postcopy-ram.c
@@ -949,6 +949,121 @@ int postcopy_wake_shared(struct PostCopyFD *pcfd,
                        pagesize);
 }
 
+/*
+ * Load a single guest page from source file into the buffer.
+ * NOTE: This is not an atomic operation and should not be used to directly load
+ * pages on page faults in postcopy. It is meant to fill in buffer that can then
+ * be copied into the faulting location using UFFDIO_COPY.
+ */
+static bool postcopy_mapped_ram_load_guest_page(MigrationIncomingState *mis,
+                                                RAMBlock *rb,
+                                                ram_addr_t rb_offset, void *buf,
+                                                Error **errp)
+{
+    ERRP_GUARD();
+    size_t page = rb_offset / qemu_target_page_size();
+    size_t read;
+
+    if (test_bit(page, rb->file_bmap)) {
+        /*
+         * This can happen concurrently, but it's thread-safe because
+         * qemu_get_buffer_at() is thread-safe, and the caller will be using
+         * different temporary buffers.
+         */
+        read =
+            qemu_get_buffer_at(mis->from_src_file, buf, qemu_target_page_size(),
+                               rb->pages_offset + rb_offset, errp);
+
+        if (read != qemu_target_page_size()) {
+            error_prepend(errp,
+                          "Could not read page %zu from RAM Block %s: ", page,
+                          rb->idstr);
+            return false;
+        }
+    } else {
+        memset(buf, '\0', qemu_target_page_size());
+    }
+    return true;
+}
+
+/**
+ * postcopy_mapped_ram_load_page() - Load pages required to access host address.
+ * @mis: Migration Incoming State.
+ * @rb: RAMBlock from where page is loaded.
+ * @rb_offset: Offset of target page in RAMBlock.
+ * @haddr: Base of target page where to load in page.
+ * @channel: Used to identify between threads and use corresponding temp.
+ * @errp: Set error in case of failure
+ *
+ * Load page(s) from RAMBlock covering the faulting address. We might need to
+ * load multiple pages in the case when host page size is greater than guest
+ * page size. As userfaultfd works on granularity of host pages, we might need
+ * to load guest pages in single operation.
+ *
+ * Return: True on success.
+ */
+static bool postcopy_mapped_ram_load_page(MigrationIncomingState *mis,
+                                          RAMBlock *rb, ram_addr_t rb_offset,
+                                          uint64_t haddr, int channel,
+                                          Error **errp)
+{
+    void *place_source = mis->postcopy_tmp_pages[channel].tmp_huge_page;
+    char *buffer_ptr = (char *)place_source;
+    size_t guest_pages_to_load =
+        MAX(1, qemu_ram_pagesize(rb) / qemu_target_page_size());
+    size_t host_page;
+    size_t page;
+
+    /*
+     * If guest page size is greater than host page size uffd needs to load one
+     * guest page and multiple host pages, hence the offsets need to aligned
+     * with guest pages (which is automatically aligned with host pages). In the
+     * same case we need to check range of bits on pending_bmap(bit per host
+     * page) to decide whether all the page have been loaded
+     */
+    rb_offset = ROUND_DOWN(rb_offset, qemu_target_page_size());
+    haddr = ROUND_DOWN(haddr, qemu_target_page_size());
+    host_page = rb_offset / qemu_ram_pagesize(rb);
+    page = rb_offset >> qemu_target_page_bits();
+
+    if (bitmap_test_and_clear_atomic(
+            rb->pending_bmap, host_page,
+            MAX(1, qemu_target_page_size() / qemu_ram_pagesize(rb)))) {
+        if (find_next_bit(rb->file_bmap, page + guest_pages_to_load, page) ==
+            page + guest_pages_to_load) {
+            /* It is efficient to use UFFDIO_ZERO if all pages are zero */
+            if (postcopy_place_page_zero(mis, (void *)haddr, rb)) {
+                error_setg(errp,
+                           "Failed to place zero page %zu from RAM Block %s at "
+                           "address %" PRIu64,
+                           page, rb->idstr, haddr);
+                return false;
+            }
+        } else {
+            size_t load_size = guest_pages_to_load * qemu_target_page_size();
+            size_t offset;
+
+            for (offset = 0; offset < load_size;
+                 offset += qemu_target_page_size()) {
+                if (!postcopy_mapped_ram_load_guest_page(
+                        mis, rb, rb_offset + offset, buffer_ptr + offset,
+                        errp)) {
+                    return false;
+                }
+            }
+
+            if (postcopy_place_page(mis, (void *)haddr, place_source, rb)) {
+                error_setg(errp,
+                           "Failed to place page %zu from RAM Block %s at "
+                           "address %" PRIu64,
+                           page, rb->idstr, haddr);
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
 /*
  * NOTE: @tid is only used when postcopy-blocktime feature is enabled, and
  * also optional: when zero is provided, the fault accounting will be ignored.
@@ -1310,6 +1425,7 @@ static void *postcopy_ram_fault_thread(void *opaque)
     int ret;
     size_t index;
     RAMBlock *rb = NULL;
+    Error *local_err = NULL;
 
     trace_postcopy_ram_fault_thread_entry();
     rcu_register_thread();
@@ -1351,11 +1467,13 @@ static void *postcopy_ram_fault_thread(void *opaque)
             break;
         }
 
-        if (!mis->to_src_file) {
+        if (!migrate_mapped_ram() && !mis->to_src_file) {
             /*
-             * Possibly someone tells us that the return path is
-             * broken already using the event. We should hold until
-             * the channel is rebuilt.
+             * Possibly someone tells us that the return path is broken already
+             * using the event. We should hold until the channel is rebuilt.
+             * Fast snapshot load doesn't support pause and recover, because
+             * it's not necessary: we can fail right away when QEMU just booted
+             * with nothing to lose.
              */
             postcopy_pause_fault_thread(mis);
         }
@@ -1418,18 +1536,37 @@ static void *postcopy_ram_fault_thread(void *opaque)
                                                 qemu_ram_get_idstr(rb),
                                                 rb_offset,
                                                 msg.arg.pagefault.feat.ptid);
+
+            if (migrate_mapped_ram()) {
+                /* Load page directly in case of fast snapshot load */
+
+                uintptr_t aligned = (uintptr_t)ROUND_DOWN(
+                    msg.arg.pagefault.address, qemu_ram_pagesize(rb));
+
+                if (try_mark_postcopy_blocktime_begin(
+                        mis, rb, rb_offset, (uintptr_t)aligned,
+                        msg.arg.pagefault.feat.ptid)) {
+                    if (!postcopy_mapped_ram_load_page(
+                            mis, rb, rb_offset, aligned, RAM_CHANNEL_POSTCOPY,
+                            &local_err)) {
+                        error_report_err(local_err);
+                        break;
+                    }
+                }
+            } else {
 retry:
-            /*
-             * Send the request to the source - we want to request one
-             * of our host page sizes (which is >= TPS)
-             */
-            ret = postcopy_request_page(mis, rb, rb_offset,
-                                        msg.arg.pagefault.address,
-                                        msg.arg.pagefault.feat.ptid);
-            if (ret) {
-                /* May be network failure, try to wait for recovery */
-                postcopy_pause_fault_thread(mis);
-                goto retry;
+                /*
+                 * Send the request to the source - we want to request one
+                 * of our host page sizes (which is >= TPS)
+                 */
+                ret = postcopy_request_page(mis, rb, rb_offset,
+                                            msg.arg.pagefault.address,
+                                            msg.arg.pagefault.feat.ptid);
+                if (ret) {
+                    /* May be network failure, try to wait for recovery */
+                    postcopy_pause_fault_thread(mis);
+                    goto retry;
+                }
             }
         }
 
@@ -1501,8 +1638,11 @@ static int postcopy_temp_pages_setup(MigrationIncomingState *mis, Error **errp)
     unsigned i, channels;
     void *temp_page;
 
-    if (migrate_postcopy_preempt()) {
-        /* If preemption enabled, need extra channel for urgent requests */
+    if (migrate_postcopy_preempt() || migrate_mapped_ram()) {
+        /*
+         * If preemption enabled or it is fast snapshot load, need extra channel
+         * for urgent requests/faults
+         */
         mis->postcopy_channels = RAM_CHANNEL_MAX;
     } else {
         /* Both precopy/postcopy on the same channel */
-- 
2.55.0



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

* [PATCH v4 08/11] migration: add eager load thread and setup for fast snapshot load
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
                   ` (6 preceding siblings ...)
  2026-08-01  2:36 ` [PATCH v4 07/11] migration: add support for fault thread to load pages from disk Aadeshveer Singh
@ 2026-08-01  2:36 ` Aadeshveer Singh
  2026-08-10 15:48   ` Juraj Marcin
  2026-08-01  2:36 ` [PATCH v4 09/11] migration: update capability conflict test for postcopy-ram+mapped-ram Aadeshveer Singh
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

In fast snapshot load a thread is needed for actively loading in pages
along with the fault path so that the guest is not dependent on fault
thread indefinitely. Considering the difference from usual network
postcopy where major chunk of RAM is already loaded here entire RAM
needs to be loaded later. Existance of background pages which are not
really accessed by the guest might never be loaded and system will be
locked in migration for indefinite time. As there should be no
assumption about how guest accesses memory, the load times can be
indefinite.

Add postcopy_ram_eager_load_thread(), for the eager thread which
iterates over all non ignored blocks calling ram_block_load_eager()
on each. ram_block_load_eager then iterates to load in all pages using
postcopy_mapped_ram_load_page(), with a different channel, which takes
care of not loading in pages already loaded by fault thread. On
completion the thread schedules postcopy_incoming_complete_bh() to
destroy the incoming migration state.

Add postcopy_ram_eager_load_setup() to create the thread. Added joining
logic in postcopy_incoming_cleanup().

Add tracepoints for entry and exit to eager load thread.

When both mapped-ram and postcopy-ram are set, divert from
qemu_loadvm_state to run fast snapshot load

Initialize postcopy RAM state and register RAM Blocks with userfaultfd
via ram_postcopy_incoming_init() and postcopy_ram_incoming_setup() in
process_incoming_migration_co(). Fault thread needs to be launched
before VM to serve faults for some hardwares emulation that need to read
RAM (like vapic devices). Populate bitmaps and offset tables while
reading file in qemu_loadvm_state_main.

Add function qemu_loadvm_run_fast_snapshot_load() which starts the VM
using loadvm_postcopy_handle_run_bh() and launches eager load thread.

Skip scheduling process_incoming_migration_bh() in
process_incoming_migration_co(), for fast snapshot load as the state
cleanup is managed by eager load thread on completion.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
---
 migration/migration.c    | 36 +++++++++++++++++++--
 migration/migration.h    |  5 +++
 migration/postcopy-ram.c | 69 ++++++++++++++++++++++++++++++++++++++++
 migration/postcopy-ram.h |  2 ++
 migration/savevm.c       | 16 ++++++++++
 migration/savevm.h       |  2 ++
 migration/trace-events   |  2 ++
 7 files changed, 130 insertions(+), 2 deletions(-)

diff --git a/migration/migration.c b/migration/migration.c
index 07394d8eea..e9c4eee6b9 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -710,6 +710,11 @@ static void process_incoming_migration_bh(void *opaque)
     migration_incoming_state_destroy();
 }
 
+static bool migration_incoming_has_postcopy_thread(MigrationIncomingState *mis)
+{
+    return mis->have_listen_thread || mis->have_eager_load_thread;
+}
+
 static void coroutine_fn
 process_incoming_migration_co(void *opaque)
 {
@@ -739,17 +744,44 @@ process_incoming_migration_co(void *opaque)
     migrate_set_state(&mis->state, MIGRATION_STATUS_SETUP,
                       MIGRATION_STATUS_ACTIVE);
 
+    /*
+     * When loading snapshot with postcopy enabled, setup the postcopy
+     * infrastructure before loading the major part of device states.
+     * It's required because qemu_loadvm_state() may access guest memory
+     * while loading device states, which can cause page faults already.
+     */
+    if (migrate_postcopy_ram() && migrate_mapped_ram()) {
+        migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE,
+                          MIGRATION_STATUS_POSTCOPY_DEVICE);
+
+        if (ram_postcopy_incoming_init(mis, &local_err)) {
+            goto fail;
+        }
+
+        postcopy_state_set(POSTCOPY_INCOMING_LISTENING);
+        if (postcopy_ram_incoming_setup(mis, &local_err)) {
+            goto fail;
+        }
+    }
+
     mis->loadvm_co = qemu_coroutine_self();
     ret = qemu_loadvm_state(mis->from_src_file, &local_err);
     mis->loadvm_co = NULL;
+    if (ret < 0) {
+        goto fail;
+    }
+
+    if (migrate_postcopy_ram() && migrate_mapped_ram()) {
+        qemu_loadvm_run_fast_snapshot_load(mis->from_src_file, mis);
+    }
 
     trace_vmstate_downtime_checkpoint("dst-precopy-loadvm-completed");
 
     trace_process_incoming_migration_co_end(ret);
-    if (mis->have_listen_thread) {
+    if (migration_incoming_has_postcopy_thread(mis)) {
         /*
          * Postcopy was started, cleanup should happen at the end of the
-         * postcopy listen thread.
+         * postcopy listen thread or eager load thread.
          */
         trace_process_incoming_migration_co_postcopy_end_main();
         goto out;
diff --git a/migration/migration.h b/migration/migration.h
index 841f49b215..540124bc27 100644
--- a/migration/migration.h
+++ b/migration/migration.h
@@ -42,6 +42,7 @@
 #define  MIGRATION_THREAD_DST_FAULT         "mig/dst/fault"
 #define  MIGRATION_THREAD_DST_LISTEN        "mig/dst/listen"
 #define  MIGRATION_THREAD_DST_PREEMPT       "mig/dst/preempt"
+#define  MIGRATION_THREAD_DST_SNAPSHOT_LOAD "mig/dst/snapshot_load"
 
 struct PostcopyBlocktimeContext;
 typedef struct ThreadPool ThreadPool;
@@ -120,6 +121,10 @@ struct MigrationIncomingState {
     bool           have_listen_thread;
     QemuThread     listen_thread;
 
+    /* Thread to load pages eagerly in fast snapshot load case */
+    bool have_eager_load_thread;
+    QemuThread eager_load_thread;
+
     /* For the kernel to send us notifications */
     int       userfault_fd;
     /* To notify the fault_thread to wake, e.g., when need to quit */
diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
index 1da9c4309b..8bfe04523a 100644
--- a/migration/postcopy-ram.c
+++ b/migration/postcopy-ram.c
@@ -2395,9 +2395,78 @@ int postcopy_incoming_cleanup(MigrationIncomingState *mis)
         mis->have_listen_thread = false;
     }
 
+    if (mis->have_eager_load_thread) {
+        qemu_thread_join(&mis->eager_load_thread);
+        mis->have_eager_load_thread = false;
+    }
+
     if (migrate_postcopy_ram()) {
         rc = postcopy_ram_incoming_cleanup(mis);
     }
 
     return rc;
 }
+
+/*
+ * Called by postcopy_ram_eager_load_thread over all blocks to load in all the
+ * pending pages of given ram block
+ */
+static int ram_block_load_eager(RAMBlock *rb, void *opaque)
+{
+    MigrationIncomingState *mis = migration_incoming_get_current();
+    MigrationState *s = migrate_get_current();
+    Error *errp = NULL;
+    void *host = qemu_ram_get_host_addr(rb);
+    void *target;
+
+    for (ram_addr_t page_loc = 0; page_loc < rb->used_length;
+         page_loc += qemu_ram_pagesize(rb)) {
+        target = (uint8_t *)host + page_loc;
+        if (!postcopy_mapped_ram_load_page(mis, rb, page_loc, (uint64_t)target,
+                                           RAM_CHANNEL_PRECOPY, &errp)) {
+            migrate_error_propagate(s, errp);
+            return -1;
+        }
+    }
+    return 0;
+}
+
+/*
+ * Used by fast snapshot load to eagerly load in all pages of RAM and schedule
+ * cleanup after entire RAM is loaded
+ */
+static void *postcopy_ram_eager_load_thread(void *opaque)
+{
+    MigrationIncomingState *mis = opaque;
+    MigrationStatus next_state;
+
+    trace_postcopy_ram_eager_load_thread_entry();
+    rcu_register_thread();
+    qemu_event_set(&mis->thread_sync_event);
+
+    if (foreach_not_ignored_block(ram_block_load_eager, NULL)) {
+        next_state = MIGRATION_STATUS_FAILED;
+    } else {
+        next_state = MIGRATION_STATUS_COMPLETED;
+    }
+    migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
+                      next_state);
+
+    postcopy_state_set(POSTCOPY_INCOMING_END);
+    migration_bh_schedule(postcopy_incoming_complete_bh, mis);
+
+    rcu_unregister_thread();
+    trace_postcopy_ram_eager_load_thread_exit();
+    return NULL;
+}
+
+/*
+ * Create thread for eager loading in fast snapshot load case
+ */
+void postcopy_ram_eager_load_setup(MigrationIncomingState *mis)
+{
+    postcopy_thread_create(
+        mis, &mis->eager_load_thread, MIGRATION_THREAD_DST_SNAPSHOT_LOAD,
+        postcopy_ram_eager_load_thread, QEMU_THREAD_JOINABLE);
+    mis->have_eager_load_thread = true;
+}
diff --git a/migration/postcopy-ram.h b/migration/postcopy-ram.h
index ea879f0bc6..c58a60bc8a 100644
--- a/migration/postcopy-ram.h
+++ b/migration/postcopy-ram.h
@@ -205,4 +205,6 @@ void mark_postcopy_blocktime_begin(uintptr_t addr, uint32_t ptid,
 int postcopy_incoming_setup(MigrationIncomingState *mis, Error **errp);
 int postcopy_incoming_cleanup(MigrationIncomingState *mis);
 
+void postcopy_ram_eager_load_setup(MigrationIncomingState *mis);
+
 #endif
diff --git a/migration/savevm.c b/migration/savevm.c
index 23adaf9dd9..08f614349b 100644
--- a/migration/savevm.c
+++ b/migration/savevm.c
@@ -2959,6 +2959,22 @@ static bool postcopy_pause_incoming(MigrationIncomingState *mis)
     return true;
 }
 
+/*
+ * Starts the VM and launches the eager thread for fast snapshot load
+ */
+void qemu_loadvm_run_fast_snapshot_load(QEMUFile *f,
+                                        MigrationIncomingState *mis)
+{
+    postcopy_state_set(POSTCOPY_INCOMING_RUNNING);
+
+    migration_bh_schedule(loadvm_postcopy_handle_run_bh, mis);
+
+    migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_DEVICE,
+                      MIGRATION_STATUS_POSTCOPY_ACTIVE);
+
+    postcopy_ram_eager_load_setup(mis);
+}
+
 int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
                            Error **errp)
 {
diff --git a/migration/savevm.h b/migration/savevm.h
index 96fdf96d4e..27c6becbc3 100644
--- a/migration/savevm.h
+++ b/migration/savevm.h
@@ -67,6 +67,8 @@ void qemu_savevm_send_postcopy_ram_discard(QEMUFile *f, const char *name,
 int qemu_save_device_state(QEMUFile *f, Error **errp);
 int qemu_loadvm_state(QEMUFile *f, Error **errp);
 void qemu_loadvm_state_cleanup(MigrationIncomingState *mis);
+void qemu_loadvm_run_fast_snapshot_load(QEMUFile *f,
+                                        MigrationIncomingState *mis);
 int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
                            Error **errp);
 int qemu_load_device_state(QEMUFile *f, Error **errp);
diff --git a/migration/trace-events b/migration/trace-events
index de99d976ab..38f11e1e9f 100644
--- a/migration/trace-events
+++ b/migration/trace-events
@@ -314,6 +314,8 @@ postcopy_blocktime_tid_cpu_map(int cpu, uint32_t tid) "cpu: %d, tid: %u"
 postcopy_blocktime_begin(uint64_t addr, uint64_t time, int cpu, bool exists) "addr: 0x%" PRIx64 ", time: %" PRIu64 ", cpu: %d, exist: %d"
 postcopy_blocktime_end(uint64_t addr, uint64_t time, int affected_cpu, int affected_non_cpus) "addr: 0x%" PRIx64 ", time: %" PRIu64 ", affected_cpus: %d, affected_non_cpus: %d"
 postcopy_blocktime_end_one(int cpu, uint8_t left_faults) "cpu: %d, left_faults: %" PRIu8
+postcopy_ram_eager_load_thread_entry(void) ""
+postcopy_ram_eager_load_thread_exit(void) ""
 
 # exec.c
 migration_exec_outgoing(const char *cmd) "cmd=%s"
-- 
2.55.0



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

* [PATCH v4 09/11] migration: update capability conflict test for postcopy-ram+mapped-ram
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
                   ` (7 preceding siblings ...)
  2026-08-01  2:36 ` [PATCH v4 08/11] migration: add eager load thread and setup for fast snapshot load Aadeshveer Singh
@ 2026-08-01  2:36 ` Aadeshveer Singh
  2026-08-10 15:49   ` Juraj Marcin
  2026-08-10 18:23   ` Peter Xu
  2026-08-01  2:36 ` [PATCH v4 10/11] migration/tests: Add test for fast snapshot load Aadeshveer Singh
                   ` (2 subsequent siblings)
  11 siblings, 2 replies; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

Remove the test test_validate_caps_pair, which asserted postcopy-ram
and mapped-ram capabilities cannot be active together. The new fast
snapshot load feature is exactly this pair of capabilities active
together, with the following patches in this series, this combination
will now supported and be functional.

Remove the capability check that rejected mapped-ram and postcopy-ram
being set simultaneously, as this combination now corresponds to fast
snapshot load.

Add a capability check against setting multifd when configured for fast
snapshot load as it is not supported yet. Also add capability check
against setting postcopy-preempt as it is incompatible.

Add infrastructure to check postcopy_notifier_list being empty in
capaility checking to block vhost-user with fast snapshot load as it is
not supported.

A smoke test exercising this feature has been added further in this
series.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
---
 include/qemu/notify.h              |  2 ++
 migration/options.c                | 20 ++++++++++--
 migration/postcopy-ram.c           |  5 +++
 migration/postcopy-ram.h           |  1 +
 tests/qtest/migration/misc-tests.c | 52 ------------------------------
 util/notify.c                      |  5 +++
 6 files changed, 31 insertions(+), 54 deletions(-)

diff --git a/include/qemu/notify.h b/include/qemu/notify.h
index abf18dbf59..666d13f727 100644
--- a/include/qemu/notify.h
+++ b/include/qemu/notify.h
@@ -75,4 +75,6 @@ void notifier_with_return_remove(NotifierWithReturn *notifier);
 int notifier_with_return_list_notify(NotifierWithReturnList *list,
                                      void *data, Error **errp);
 
+bool notifier_with_return_list_empty(NotifierWithReturnList *list);
+
 #endif
diff --git a/migration/options.c b/migration/options.c
index 5cbfd29099..4f8cc5fd6e 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -727,10 +727,26 @@ bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp)
                        "Mapped-ram migration is incompatible with xbzrle");
             return false;
         }
+    }
+
+    if (new_caps[MIGRATION_CAPABILITY_MAPPED_RAM] &&
+        new_caps[MIGRATION_CAPABILITY_POSTCOPY_RAM]) {
+        if (new_caps[MIGRATION_CAPABILITY_MULTIFD]) {
+            error_setg(errp,
+                       "Multifd is not supported with fast snapshot load");
+            return false;
+        }
+
+        if (new_caps[MIGRATION_CAPABILITY_POSTCOPY_PREEMPT]) {
+            error_setg(
+                errp,
+                "Postcopy Preempt is incompatible with fast snapshot load");
+            return false;
+        }
 
-        if (new_caps[MIGRATION_CAPABILITY_POSTCOPY_RAM]) {
+        if (!postcopy_notifier_list_empty()) {
             error_setg(errp,
-                       "Mapped-ram migration is incompatible with postcopy");
+                       "vhost-user is not supported with fast snapshot load");
             return false;
         }
     }
diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
index 8bfe04523a..e01ac628f5 100644
--- a/migration/postcopy-ram.c
+++ b/migration/postcopy-ram.c
@@ -82,6 +82,11 @@ int postcopy_notify(enum PostcopyNotifyReason reason, Error **errp)
                                             &pnd, errp);
 }
 
+bool postcopy_notifier_list_empty(void)
+{
+    return notifier_with_return_list_empty(&postcopy_notifier_list);
+}
+
 /*
  * NOTE: this routine is not thread safe, we can't call it concurrently. But it
  * should be good enough for migration's purposes.
diff --git a/migration/postcopy-ram.h b/migration/postcopy-ram.h
index c58a60bc8a..edba7b0240 100644
--- a/migration/postcopy-ram.h
+++ b/migration/postcopy-ram.h
@@ -136,6 +136,7 @@ void postcopy_add_notifier(NotifierWithReturn *nn);
 void postcopy_remove_notifier(NotifierWithReturn *n);
 /* Call the notifier list set by postcopy_add_start_notifier */
 int postcopy_notify(enum PostcopyNotifyReason reason, Error **errp);
+bool postcopy_notifier_list_empty(void);
 
 void postcopy_thread_create(MigrationIncomingState *mis,
                             QemuThread *thread, const char *name,
diff --git a/tests/qtest/migration/misc-tests.c b/tests/qtest/migration/misc-tests.c
index ec6d438cdc..4e0deb7f18 100644
--- a/tests/qtest/migration/misc-tests.c
+++ b/tests/qtest/migration/misc-tests.c
@@ -201,55 +201,6 @@ static void do_test_validate_uri_channel(MigrateCommon *args)
     migrate_end(from, to, false);
 }
 
-static void validate_caps_pair(QTestState *from,
-                               const char *first_capability,
-                               const char *second_capability,
-                               const char *expected_error)
-{
-    QDict *rsp;
-    const char *error_desc;
-
-    migrate_set_capability(from, first_capability, true);
-
-    rsp = qtest_qmp_assert_failure_ref(
-        from,
-        "{ 'execute': 'migrate-set-capabilities',"
-        "  'arguments': { 'capabilities': [ { "
-        "      'capability': %s, 'state': true } ] } }",
-        second_capability);
-
-    error_desc = qdict_get_str(rsp, "desc");
-    g_assert_cmpstr(error_desc, ==, expected_error);
-    qobject_unref(rsp);
-
-    migrate_set_capability(from, first_capability, false);
-}
-
-static void test_validate_caps_pair(char *test_path, MigrateCommon *args)
-{
-    g_autofree char *serial_path = g_strconcat(tmpfs, "/src_serial", NULL);
-    g_autofree char *cap_pair = g_path_get_basename(test_path);
-    QTestState *from, *to;
-
-    args->start.hide_stderr = true;
-    args->start.only_source = true;
-
-    if (migrate_start(&from, &to, &args->start)) {
-        return;
-    }
-
-    if (g_str_equal(cap_pair, "mapped_ram_postcopy")) {
-        const char *error =
-            "Mapped-ram migration is incompatible with postcopy";
-
-        validate_caps_pair(from, "mapped-ram", "postcopy-ram", error);
-        validate_caps_pair(from, "postcopy-ram", "mapped-ram", error);
-    }
-
-    qtest_quit(from);
-    unlink(serial_path);
-}
-
 static void test_validate_uri_channels_both_set(char *name, MigrateCommon *args)
 {
     args->uri = "tcp:127.0.0.1:0",
@@ -309,7 +260,4 @@ void migration_test_add_misc(MigrationTestEnv *env)
                        test_validate_uri_channels_both_set);
     migration_test_add("/migration/validate_uri/channels/none_set",
                        test_validate_uri_channels_none_set);
-    migration_test_add_suffix("/migration/validate_caps/",
-                              "mapped_ram_postcopy",
-                              test_validate_caps_pair);
 }
diff --git a/util/notify.c b/util/notify.c
index c6e158ffb3..8d2657e411 100644
--- a/util/notify.c
+++ b/util/notify.c
@@ -75,3 +75,8 @@ int notifier_with_return_list_notify(NotifierWithReturnList *list, void *data,
     }
     return ret;
 }
+
+bool notifier_with_return_list_empty(NotifierWithReturnList *list)
+{
+    return QLIST_EMPTY(&list->notifiers);
+}
-- 
2.55.0



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

* [PATCH v4 10/11] migration/tests: Add test for fast snapshot load
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
                   ` (8 preceding siblings ...)
  2026-08-01  2:36 ` [PATCH v4 09/11] migration: update capability conflict test for postcopy-ram+mapped-ram Aadeshveer Singh
@ 2026-08-01  2:36 ` Aadeshveer Singh
  2026-08-10 15:50   ` Juraj Marcin
  2026-08-01  2:36 ` [PATCH v4 11/11] docs/migration: Add documentation for fast snapshot load feature Aadeshveer Singh
  2026-08-10 19:02 ` [PATCH v4 00/11] migration: fast snapshot load Peter Xu
  11 siblings, 1 reply; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

Add test_postcopy_file_mapped_ram() to test for saving a snapshot in
mapped ram format and loading it using postcopy/fast snapshot load.

Test is similar to test_precopy_file_mapped_ram() with additional
start_hook to set postcopy capabilities on the receiving end.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
---
 tests/qtest/migration/file-tests.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/tests/qtest/migration/file-tests.c b/tests/qtest/migration/file-tests.c
index 5118d9dec9..c7e4b05c61 100644
--- a/tests/qtest/migration/file-tests.c
+++ b/tests/qtest/migration/file-tests.c
@@ -190,6 +190,25 @@ static void test_multifd_file_mapped_ram_dio(char *name, MigrateCommon *args)
     test_file_common(args, true);
 }
 
+static void *migrate_hook_start_postcopy_mapped_ram(QTestState *from,
+                                                    QTestState *to)
+{
+    migrate_set_capability(
+        to, MigrationCapability_lookup.array[MIGRATION_CAPABILITY_POSTCOPY_RAM],
+        true);
+
+    return NULL;
+}
+
+static void test_postcopy_file_mapped_ram(char *name, MigrateCommon *args)
+{
+    args->start.caps[MIGRATION_CAPABILITY_MAPPED_RAM] = true;
+
+    args->start_hook = migrate_hook_start_postcopy_mapped_ram;
+
+    test_file_common(args, false);
+}
+
 #ifndef _WIN32
 static void migrate_hook_end_multifd_mapped_ram_fdset(QTestState *from,
                                                       QTestState *to,
@@ -330,6 +349,11 @@ void migration_test_add_file(MigrationTestEnv *env)
     migration_test_add("/migration/multifd/file/mapped-ram/live",
                        test_multifd_file_mapped_ram_live);
 
+    if (env->has_uffd) {
+        migration_test_add("/migration/postcopy/file/mapped-ram",
+                           test_postcopy_file_mapped_ram);
+    }
+
 #ifndef _WIN32
     migration_test_add("/migration/multifd/file/mapped-ram/fdset",
                        test_multifd_file_mapped_ram_fdset);
-- 
2.55.0



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

* [PATCH v4 11/11] docs/migration: Add documentation for fast snapshot load feature
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
                   ` (9 preceding siblings ...)
  2026-08-01  2:36 ` [PATCH v4 10/11] migration/tests: Add test for fast snapshot load Aadeshveer Singh
@ 2026-08-01  2:36 ` Aadeshveer Singh
  2026-08-10 18:24   ` Peter Xu
  2026-08-10 19:02 ` [PATCH v4 00/11] migration: fast snapshot load Peter Xu
  11 siblings, 1 reply; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-01  2:36 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier, Aadeshveer Singh

Add documenatation for fast snapshot load covering an overview, the
architecture, usage and limitations.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
---
 docs/devel/migration/fast-snapshot-load.rst | 82 +++++++++++++++++++++
 docs/devel/migration/features.rst           |  1 +
 2 files changed, 83 insertions(+)
 create mode 100644 docs/devel/migration/fast-snapshot-load.rst

diff --git a/docs/devel/migration/fast-snapshot-load.rst b/docs/devel/migration/fast-snapshot-load.rst
new file mode 100644
index 0000000000..fe2361d6fe
--- /dev/null
+++ b/docs/devel/migration/fast-snapshot-load.rst
@@ -0,0 +1,82 @@
+==================
+Fast Snapshot Load
+==================
+
+Overview
+========
+Fast snapshot load is an extension of the postcopy migration feature
+to disk loads.
+
+Unlike a usual snapshot load, which requires all VM data (RAM as well
+as device states) to be loaded into host RAM from the snapshot file
+for the guest to run, fast snapshot load uses postcopy infrastructure
+to load in only the required device states and allows RAM pages to
+be loaded after the guest starts execution. The idea is to start the
+guest and serve its page faults on the go, reducing the perceived
+resume time for large snapshots.
+
+Architecture
+============
+This feature combines postcopy migration and mapped-ram capabilities
+to load RAM pages on demand. It is done by catching guest faults using
+Linux ``userfaultfd`` and loading the page by calculating the offset
+of its location in the snapshot file using mapped-ram capabilities.
+
+Fault Thread
+------------
+The fault thread uses Linux ``userfaultfd`` to catch page faults caused
+by guest and directly load the page from the snapshot file. It is
+very similar to network postcopy fault thread, with primary difference
+being it loads pages directly by reading from the snapshot file.
+
+Eager Thread
+------------
+Eager thread iterates over all pages in RAM and loads each page not
+yet loaded by fault thread. It is required as unlike network postcopy
+where majority of RAM has already been loaded via precopy, here entire
+RAM is waiting to be loaded. If there is no eager loading thread each
+page will only be loaded when it is required by guest. In case there
+are some background pages that are never/rarely accessed by guest,
+the system will be locked in migration state indefinitely.
+
+Synchronization
+---------------
+In order to make sure both of these threads do not load the same page
+twice potentially overwriting and corrupting user RAM, a bitmap is
+used (``RAMBlock->pending_bmap``) which tracks the pages claimed to
+be loaded by threads. This prevents race condition when one thread
+is loading the page and other one tries to do the same.
+
+Usage
+=====
+
+Simply enable ``mapped-ram`` and ``postcopy-ram`` capabilities on
+the destination:
+
+.. code-block:: text
+
+    migrate_set_capability mapped-ram on
+    migrate_set_capability postcopy-ram on
+
+Use a ``file:`` URI for migration:
+
+.. code-block:: text
+
+    migrate_incoming file:/path/to/snapshot/file
+
+Limitations
+===========
+
+ - Multifd
+    Fast snapshot load is currently incompatible with ``multifd``
+    capability. While ``mapped-ram`` allows for parallel disk I/O,
+    coupling it with ``postcopy`` capability requires additional
+    infrastructure.
+
+ - Host OS support
+    Because this feautre essentially depends on ``userfaultfd``
+    to trap page faults, it is supported only on Linux hosts.
+
+ - vhost-user
+    Fast snapshot load does not currently support ``vhost-user``
+    backends.
diff --git a/docs/devel/migration/features.rst b/docs/devel/migration/features.rst
index 9aef79e7fa..23c2a93173 100644
--- a/docs/devel/migration/features.rst
+++ b/docs/devel/migration/features.rst
@@ -11,6 +11,7 @@ Migration has plenty of features to support different use cases.
    vfio
    virtio
    mapped-ram
+   fast-snapshot-load
    CPR
    qpl-compression
    uadk-compression
-- 
2.55.0



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

* Re: [PATCH v4 07/11] migration: add support for fault thread to load pages from disk
  2026-08-01  2:36 ` [PATCH v4 07/11] migration: add support for fault thread to load pages from disk Aadeshveer Singh
@ 2026-08-08  4:34   ` Aadeshveer Singh
  2026-08-10 15:40     ` Peter Xu
  0 siblings, 1 reply; 27+ messages in thread
From: Aadeshveer Singh @ 2026-08-08  4:34 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

A subtle concurrency bug exists in the current patch; this small patch
should fix it.

Current patch uses bitmap_test_and_clear_atomic assuming it performs
an atomic operation over a range, which as pointer out by Peter is not
the case. It performs atomic word by word operations making the
overall operation unsafe.
Therefore, keeping the same bitmap would require protection by a mutex
lock which might not be as efficient. Hence this small patch resizes
the pending_bmap to have exactly one bit per load operation(loading
the larger of host page and guest page).

Thank you,
Aadeshveer Singh

diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
index e01ac628f5..0bf0f837ad 100644
--- a/migration/postcopy-ram.c
+++ b/migration/postcopy-ram.c
@@ -1031,9 +1031,13 @@ static bool
postcopy_mapped_ram_load_page(MigrationIncomingState *mis,
     host_page = rb_offset / qemu_ram_pagesize(rb);
     page = rb_offset >> qemu_target_page_bits();

-    if (bitmap_test_and_clear_atomic(
-            rb->pending_bmap, host_page,
-            MAX(1, qemu_target_page_size() / qemu_ram_pagesize(rb)))) {
+    /*
+     * pending_bmap needs the index of host or guest page based on which is
+     * larger. As page index is inversely proportional to page size we use the
+     * minimum of both.
+     */
+    if (bitmap_test_and_clear_atomic(rb->pending_bmap, MIN(host_page, page),
+                                     1)) {
         if (find_next_bit(rb->file_bmap, page + guest_pages_to_load, page) ==
             page + guest_pages_to_load) {
             /* It is efficient to use UFFDIO_ZERO if all pages are zero */
diff --git a/migration/ram.c b/migration/ram.c
index 734fd90e12..52a3af9245 100644
--- a/migration/ram.c
+++ b/migration/ram.c
@@ -269,7 +269,14 @@ static void ramblock_pending_bmap_init(void)

     RAMBLOCK_FOREACH_NOT_IGNORED(rb) {
         assert(!rb->pending_bmap);
-        size_t size = rb->max_length / qemu_ram_pagesize(rb);
+        /*
+         * The pending_bmap granularity must match the maximum of
host and guest
+         * page sizes. This ensures that every load operation checks for one
+         * bit, allowing lockless thread coordination via a single-bit atomic
+         * test-and-clear.
+         */
+        size_t size = rb->max_length /
+                      MAX(qemu_ram_pagesize(rb), qemu_target_page_size());
         rb->pending_bmap = bitmap_new(size);
         bitmap_set(rb->pending_bmap, 0, size);
     }

On Sat, Aug 1, 2026 at 8:07 AM Aadeshveer Singh <aadeshveer07@gmail.com> wrote:
>
> In fast snapshot load, we would like to serve faults as soon as possible
> hence loading pages directly instead of requesting a source
>
> Add postcopy_mapped_ram_load_page() function which serves single page
> fault. It uses bitmap_test_and_clear_atomic() on pending_bmap to prevent
> multiple threads from loading same page. It loads a page or pages
> depending on size of guest pages and host pages, loading exactly the
> larger of two. If the entire page is zero postcopy_place_page_zero() is
> used for efficiency and in case some part is non zero it is part by part
> loaded on loop using new function postcopy_mapped_ram_load_guest_page()
> which loads a single guest page in a buffer which is then placed using
> postcopy_place_page(). This covers all possible cases for various page
> sizes of host and guest.
>
> Update postcopy_ram_fault_thread to call postcopy_mapped_ram_load_page
> instead of requesting source in case of fast snapshot load. to_src_file
> check is bypassed in fast snapshot load case as there is no source.
>
> Call try_mark_postcopy_blocktime_begin on every page fault to support
> postcopy-blocktime.
>
> Allocate another channel in postcopy_temp_pages_setup(like the preempt
> case), for both the fault thread and eager thread to load pages
> independently.
>
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> ---
>  migration/postcopy-ram.c | 174 +++++++++++++++++++++++++++++++++++----
>  1 file changed, 157 insertions(+), 17 deletions(-)
>
> diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
> index 2e2c9fae10..1da9c4309b 100644
> --- a/migration/postcopy-ram.c
> +++ b/migration/postcopy-ram.c
> @@ -949,6 +949,121 @@ int postcopy_wake_shared(struct PostCopyFD *pcfd,
>                         pagesize);
>  }
>
> +/*
> + * Load a single guest page from source file into the buffer.
> + * NOTE: This is not an atomic operation and should not be used to directly load
> + * pages on page faults in postcopy. It is meant to fill in buffer that can then
> + * be copied into the faulting location using UFFDIO_COPY.
> + */
> +static bool postcopy_mapped_ram_load_guest_page(MigrationIncomingState *mis,
> +                                                RAMBlock *rb,
> +                                                ram_addr_t rb_offset, void *buf,
> +                                                Error **errp)
> +{
> +    ERRP_GUARD();
> +    size_t page = rb_offset / qemu_target_page_size();
> +    size_t read;
> +
> +    if (test_bit(page, rb->file_bmap)) {
> +        /*
> +         * This can happen concurrently, but it's thread-safe because
> +         * qemu_get_buffer_at() is thread-safe, and the caller will be using
> +         * different temporary buffers.
> +         */
> +        read =
> +            qemu_get_buffer_at(mis->from_src_file, buf, qemu_target_page_size(),
> +                               rb->pages_offset + rb_offset, errp);
> +
> +        if (read != qemu_target_page_size()) {
> +            error_prepend(errp,
> +                          "Could not read page %zu from RAM Block %s: ", page,
> +                          rb->idstr);
> +            return false;
> +        }
> +    } else {
> +        memset(buf, '\0', qemu_target_page_size());
> +    }
> +    return true;
> +}
> +
> +/**
> + * postcopy_mapped_ram_load_page() - Load pages required to access host address.
> + * @mis: Migration Incoming State.
> + * @rb: RAMBlock from where page is loaded.
> + * @rb_offset: Offset of target page in RAMBlock.
> + * @haddr: Base of target page where to load in page.
> + * @channel: Used to identify between threads and use corresponding temp.
> + * @errp: Set error in case of failure
> + *
> + * Load page(s) from RAMBlock covering the faulting address. We might need to
> + * load multiple pages in the case when host page size is greater than guest
> + * page size. As userfaultfd works on granularity of host pages, we might need
> + * to load guest pages in single operation.
> + *
> + * Return: True on success.
> + */
> +static bool postcopy_mapped_ram_load_page(MigrationIncomingState *mis,
> +                                          RAMBlock *rb, ram_addr_t rb_offset,
> +                                          uint64_t haddr, int channel,
> +                                          Error **errp)
> +{
> +    void *place_source = mis->postcopy_tmp_pages[channel].tmp_huge_page;
> +    char *buffer_ptr = (char *)place_source;
> +    size_t guest_pages_to_load =
> +        MAX(1, qemu_ram_pagesize(rb) / qemu_target_page_size());
> +    size_t host_page;
> +    size_t page;
> +
> +    /*
> +     * If guest page size is greater than host page size uffd needs to load one
> +     * guest page and multiple host pages, hence the offsets need to aligned
> +     * with guest pages (which is automatically aligned with host pages). In the
> +     * same case we need to check range of bits on pending_bmap(bit per host
> +     * page) to decide whether all the page have been loaded
> +     */
> +    rb_offset = ROUND_DOWN(rb_offset, qemu_target_page_size());
> +    haddr = ROUND_DOWN(haddr, qemu_target_page_size());
> +    host_page = rb_offset / qemu_ram_pagesize(rb);
> +    page = rb_offset >> qemu_target_page_bits();
> +
> +    if (bitmap_test_and_clear_atomic(
> +            rb->pending_bmap, host_page,
> +            MAX(1, qemu_target_page_size() / qemu_ram_pagesize(rb)))) {
> +        if (find_next_bit(rb->file_bmap, page + guest_pages_to_load, page) ==
> +            page + guest_pages_to_load) {
> +            /* It is efficient to use UFFDIO_ZERO if all pages are zero */
> +            if (postcopy_place_page_zero(mis, (void *)haddr, rb)) {
> +                error_setg(errp,
> +                           "Failed to place zero page %zu from RAM Block %s at "
> +                           "address %" PRIu64,
> +                           page, rb->idstr, haddr);
> +                return false;
> +            }
> +        } else {
> +            size_t load_size = guest_pages_to_load * qemu_target_page_size();
> +            size_t offset;
> +
> +            for (offset = 0; offset < load_size;
> +                 offset += qemu_target_page_size()) {
> +                if (!postcopy_mapped_ram_load_guest_page(
> +                        mis, rb, rb_offset + offset, buffer_ptr + offset,
> +                        errp)) {
> +                    return false;
> +                }
> +            }
> +
> +            if (postcopy_place_page(mis, (void *)haddr, place_source, rb)) {
> +                error_setg(errp,
> +                           "Failed to place page %zu from RAM Block %s at "
> +                           "address %" PRIu64,
> +                           page, rb->idstr, haddr);
> +                return false;
> +            }
> +        }
> +    }
> +    return true;
> +}
> +
>  /*
>   * NOTE: @tid is only used when postcopy-blocktime feature is enabled, and
>   * also optional: when zero is provided, the fault accounting will be ignored.
> @@ -1310,6 +1425,7 @@ static void *postcopy_ram_fault_thread(void *opaque)
>      int ret;
>      size_t index;
>      RAMBlock *rb = NULL;
> +    Error *local_err = NULL;
>
>      trace_postcopy_ram_fault_thread_entry();
>      rcu_register_thread();
> @@ -1351,11 +1467,13 @@ static void *postcopy_ram_fault_thread(void *opaque)
>              break;
>          }
>
> -        if (!mis->to_src_file) {
> +        if (!migrate_mapped_ram() && !mis->to_src_file) {
>              /*
> -             * Possibly someone tells us that the return path is
> -             * broken already using the event. We should hold until
> -             * the channel is rebuilt.
> +             * Possibly someone tells us that the return path is broken already
> +             * using the event. We should hold until the channel is rebuilt.
> +             * Fast snapshot load doesn't support pause and recover, because
> +             * it's not necessary: we can fail right away when QEMU just booted
> +             * with nothing to lose.
>               */
>              postcopy_pause_fault_thread(mis);
>          }
> @@ -1418,18 +1536,37 @@ static void *postcopy_ram_fault_thread(void *opaque)
>                                                  qemu_ram_get_idstr(rb),
>                                                  rb_offset,
>                                                  msg.arg.pagefault.feat.ptid);
> +
> +            if (migrate_mapped_ram()) {
> +                /* Load page directly in case of fast snapshot load */
> +
> +                uintptr_t aligned = (uintptr_t)ROUND_DOWN(
> +                    msg.arg.pagefault.address, qemu_ram_pagesize(rb));
> +
> +                if (try_mark_postcopy_blocktime_begin(
> +                        mis, rb, rb_offset, (uintptr_t)aligned,
> +                        msg.arg.pagefault.feat.ptid)) {
> +                    if (!postcopy_mapped_ram_load_page(
> +                            mis, rb, rb_offset, aligned, RAM_CHANNEL_POSTCOPY,
> +                            &local_err)) {
> +                        error_report_err(local_err);
> +                        break;
> +                    }
> +                }
> +            } else {
>  retry:
> -            /*
> -             * Send the request to the source - we want to request one
> -             * of our host page sizes (which is >= TPS)
> -             */
> -            ret = postcopy_request_page(mis, rb, rb_offset,
> -                                        msg.arg.pagefault.address,
> -                                        msg.arg.pagefault.feat.ptid);
> -            if (ret) {
> -                /* May be network failure, try to wait for recovery */
> -                postcopy_pause_fault_thread(mis);
> -                goto retry;
> +                /*
> +                 * Send the request to the source - we want to request one
> +                 * of our host page sizes (which is >= TPS)
> +                 */
> +                ret = postcopy_request_page(mis, rb, rb_offset,
> +                                            msg.arg.pagefault.address,
> +                                            msg.arg.pagefault.feat.ptid);
> +                if (ret) {
> +                    /* May be network failure, try to wait for recovery */
> +                    postcopy_pause_fault_thread(mis);
> +                    goto retry;
> +                }
>              }
>          }
>
> @@ -1501,8 +1638,11 @@ static int postcopy_temp_pages_setup(MigrationIncomingState *mis, Error **errp)
>      unsigned i, channels;
>      void *temp_page;
>
> -    if (migrate_postcopy_preempt()) {
> -        /* If preemption enabled, need extra channel for urgent requests */
> +    if (migrate_postcopy_preempt() || migrate_mapped_ram()) {
> +        /*
> +         * If preemption enabled or it is fast snapshot load, need extra channel
> +         * for urgent requests/faults
> +         */
>          mis->postcopy_channels = RAM_CHANNEL_MAX;
>      } else {
>          /* Both precopy/postcopy on the same channel */
> --
> 2.55.0
>


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

* Re: [PATCH v4 05/11] migration: Make qemu_get_buffer_at() thread-safe
  2026-08-01  2:36 ` [PATCH v4 05/11] migration: Make qemu_get_buffer_at() thread-safe Aadeshveer Singh
@ 2026-08-10 14:14   ` Peter Xu
  2026-08-10 15:10   ` Juraj Marcin
  1 sibling, 0 replies; 27+ messages in thread
From: Peter Xu @ 2026-08-10 14:14 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On Sat, Aug 01, 2026 at 08:06:22AM +0530, Aadeshveer Singh wrote:
> Remove the internal QEMUFile error state modification from
> qemu_get_buffer_at(). This function is called by two functions, both of
> which already check for unexpected return values and handle their own
> error reporting.
> 
> Removing this shared state modification makes qemu_get_buffer_at()
> strictly thread-safe for concurrent disk reads, serving as a
> preparatory change for the upcoming fast snapshot load feature.
> 
> Removed local error by passing errp to improve on error handling,
> consequently also change caller to use error_prevent and not error_setg.
> 
> Set errp in case of f->last_error as function should set errp in case it
> fails, and caller can simply use error_prepend without checking errp in
> case of error.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>

Reviewed-by: Peter Xu <peterx@redhat.com>

-- 
Peter Xu



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

* Re: [PATCH v4 01/11] migration: Propagate error in postcopy setup functions
  2026-08-01  2:36 ` [PATCH v4 01/11] migration: Propagate error in postcopy setup functions Aadeshveer Singh
@ 2026-08-10 15:08   ` Juraj Marcin
  0 siblings, 0 replies; 27+ messages in thread
From: Juraj Marcin @ 2026-08-10 15:08 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On 2026-08-01 08:06, Aadeshveer Singh wrote:
> Modernize error handling in postcopy_ram_incoming_setup() and
> postcopy_temp_pages_setup() by replacing error_reports and local error
> handling with standard Error propagation.
> 
> Replace use of strerror() on errno with error_setg_errno() for modular
> handling of errors and change return values to -1 on failure as no
> caller checks the actual return value.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> Reviewed-by: Peter Xu <peterx@redhat.com>
> ---
>  migration/postcopy-ram.c | 41 +++++++++++++++++-----------------------
>  migration/postcopy-ram.h |  2 +-
>  2 files changed, 18 insertions(+), 25 deletions(-)

Reviewed-by: Juraj Marcin <jmarcin@redhat.com>



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

* Re: [PATCH v4 02/11] migration: Extract blocktime marking helper
  2026-08-01  2:36 ` [PATCH v4 02/11] migration: Extract blocktime marking helper Aadeshveer Singh
@ 2026-08-10 15:08   ` Juraj Marcin
  0 siblings, 0 replies; 27+ messages in thread
From: Juraj Marcin @ 2026-08-10 15:08 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On 2026-08-01 08:06, Aadeshveer Singh wrote:
> Use new function try_mark_postcopy_blocktim_begin() to call
> mark_postcopy_blocktime_begin if page was not received and return if it
> actually marked it.
> 
> This will help in having a cleaner API to call
> mark_postcopy_blocktime_begin as it requires the page to not be received
> by asserting on it's existance in RAMBlock->receivedmap.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> Reviewed-by: Peter Xu <peterx@redhat.com>
> ---
>  migration/migration.c    | 24 ++----------------------
>  migration/postcopy-ram.c | 31 +++++++++++++++++++++++++++++++
>  migration/postcopy-ram.h |  3 +++
>  3 files changed, 36 insertions(+), 22 deletions(-)

Reviewed-by: Juraj Marcin <jmarcin@redhat.com>



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

* Re: [PATCH v4 03/11] migration: Rename postcopy_listen_thread_bh
  2026-08-01  2:36 ` [PATCH v4 03/11] migration: Rename postcopy_listen_thread_bh Aadeshveer Singh
@ 2026-08-10 15:09   ` Juraj Marcin
  0 siblings, 0 replies; 27+ messages in thread
From: Juraj Marcin @ 2026-08-10 15:09 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On 2026-08-01 08:06, Aadeshveer Singh wrote:
> Rename postcopy_listen_thread_bh to postcopy_incoming_complete_bh for a
> more robust naming scheme. This will allow fast snapshot load to use
> this bh funciton directly to cleanup the incoming state.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> Reviewed-by: Peter Xu <peterx@redhat.com>
> ---
>  migration/postcopy-ram.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)

Reviewed-by: Juraj Marcin <jmarcin@redhat.com>



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

* Re: [PATCH v4 04/11] migration: Use file_bmap for RAMBlock during incoming file load
  2026-08-01  2:36 ` [PATCH v4 04/11] migration: Use file_bmap for RAMBlock during incoming file load Aadeshveer Singh
@ 2026-08-10 15:09   ` Juraj Marcin
  0 siblings, 0 replies; 27+ messages in thread
From: Juraj Marcin @ 2026-08-10 15:09 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On 2026-08-01 08:06, Aadeshveer Singh wrote:
> Replace the temporary bitmap with the existing file_bmap attribute of
> the RAMBlock. This acts as a preparatory change for the upcoming fast
> snapshot load feature.
> 
> Reusing this bitmap allows the destination to track page types during
> a postcopy load, enabling faster, direct placement of zero pages.
> 
> Since file_bmap is currently only utilized during the migration save
> phase, it can be safely repurposed during the load phase without
> introducing conflicts.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> Reviewed-by: Peter Xu <peterx@redhat.com>
> ---
>  migration/ram.c | 33 +++++++++++++++++++++++++++------
>  1 file changed, 27 insertions(+), 6 deletions(-)

Reviewed-by: Juraj Marcin <jmarcin@redhat.com>



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

* Re: [PATCH v4 05/11] migration: Make qemu_get_buffer_at() thread-safe
  2026-08-01  2:36 ` [PATCH v4 05/11] migration: Make qemu_get_buffer_at() thread-safe Aadeshveer Singh
  2026-08-10 14:14   ` Peter Xu
@ 2026-08-10 15:10   ` Juraj Marcin
  1 sibling, 0 replies; 27+ messages in thread
From: Juraj Marcin @ 2026-08-10 15:10 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On 2026-08-01 08:06, Aadeshveer Singh wrote:
> Remove the internal QEMUFile error state modification from
> qemu_get_buffer_at(). This function is called by two functions, both of
> which already check for unexpected return values and handle their own
> error reporting.
> 
> Removing this shared state modification makes qemu_get_buffer_at()
> strictly thread-safe for concurrent disk reads, serving as a
> preparatory change for the upcoming fast snapshot load feature.
> 
> Removed local error by passing errp to improve on error handling,
> consequently also change caller to use error_prevent and not error_setg.
> 
> Set errp in case of f->last_error as function should set errp in case it
> fails, and caller can simply use error_prepend without checking errp in
> case of error.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> ---
>  migration/qemu-file.c | 11 +++++------
>  migration/qemu-file.h |  4 ++--
>  migration/ram.c       |  6 +++---
>  3 files changed, 10 insertions(+), 11 deletions(-)

Reviewed-by: Juraj Marcin <jmarcin@redhat.com>



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

* Re: [PATCH v4 06/11] migration: add RAMBlock field and helper for fast snapshot load
  2026-08-01  2:36 ` [PATCH v4 06/11] migration: add RAMBlock field and helper for fast snapshot load Aadeshveer Singh
@ 2026-08-10 15:12   ` Juraj Marcin
  0 siblings, 0 replies; 27+ messages in thread
From: Juraj Marcin @ 2026-08-10 15:12 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On 2026-08-01 08:06, Aadeshveer Singh wrote:
> Add pending_bmap field per RAMBlock which is a Bitmap to store
> internal state of which pages have been read by some thread to ensure
> coordination between fault thread and eager load thread.
> 
> Modify parse_ramblock_mapped_ram(), to not load the actual RAMBlocks
> data in postcopy case as that will be loaded by fault thread and eager
> thread after the VM starts running.
> 
> Change ram_load() to use new function ram_should_load_postcopy_pages()
> to decide how to load/read RAM.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> ---
>  include/system/ramblock.h |  6 ++++
>  migration/ram.c           | 59 +++++++++++++++++++++++++++++++++++----
>  2 files changed, 59 insertions(+), 6 deletions(-)

Reviewed-by: Juraj Marcin <jmarcin@redhat.com>



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

* Re: [PATCH v4 07/11] migration: add support for fault thread to load pages from disk
  2026-08-08  4:34   ` Aadeshveer Singh
@ 2026-08-10 15:40     ` Peter Xu
  0 siblings, 0 replies; 27+ messages in thread
From: Peter Xu @ 2026-08-10 15:40 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

Aadeshveer,

Thanks for looking into this problem.  Below solution should work in
general, but there's still one problem..

On Sat, Aug 08, 2026 at 10:04:48AM +0530, Aadeshveer Singh wrote:
> A subtle concurrency bug exists in the current patch; this small patch
> should fix it.
> 
> Current patch uses bitmap_test_and_clear_atomic assuming it performs
> an atomic operation over a range, which as pointer out by Peter is not
> the case. It performs atomic word by word operations making the
> overall operation unsafe.
> Therefore, keeping the same bitmap would require protection by a mutex
> lock which might not be as efficient. Hence this small patch resizes
> the pending_bmap to have exactly one bit per load operation(loading
> the larger of host page and guest page).
> 
> Thank you,
> Aadeshveer Singh
> 
> diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
> index e01ac628f5..0bf0f837ad 100644
> --- a/migration/postcopy-ram.c
> +++ b/migration/postcopy-ram.c
> @@ -1031,9 +1031,13 @@ static bool
> postcopy_mapped_ram_load_page(MigrationIncomingState *mis,
>      host_page = rb_offset / qemu_ram_pagesize(rb);
>      page = rb_offset >> qemu_target_page_bits();
> 
> -    if (bitmap_test_and_clear_atomic(
> -            rb->pending_bmap, host_page,
> -            MAX(1, qemu_target_page_size() / qemu_ram_pagesize(rb)))) {
> +    /*
> +     * pending_bmap needs the index of host or guest page based on which is
> +     * larger. As page index is inversely proportional to page size we use the
> +     * minimum of both.
> +     */
> +    if (bitmap_test_and_clear_atomic(rb->pending_bmap, MIN(host_page, page),
> +                                     1)) {
>          if (find_next_bit(rb->file_bmap, page + guest_pages_to_load, page) ==
>              page + guest_pages_to_load) {
>              /* It is efficient to use UFFDIO_ZERO if all pages are zero */

Consider the case where guest psize > host psize, here the current code
will invoke postcopy_place_page_zero() or postcopy_place_page() only once
for each guest page. But IIUC that's not enough: we'll need to loop over
the few host pages that is covered by the same guest page.

I confess this is really a rare corner case..  so you can decide how to
"fix" it.  There's always the option to disable this feature for now when
guest psize is larger than host psize (OTOH, host psize > guest psize is
much more common, because that's normally how huge page backed VMs work on
linux systems).

Or just provide the loops over host pages, I think it will start working
and IIUC it's indeed the most efficient.  But then, to make it slightly
easier to future readers (I still think the bitmap definition will be
slightly hard to grasp for future readers.. your comments all over
hopefully will help), maybe we can also rename "page"; it implies guest
page index but it's not obvious.  We can make it "guest_page" to match
"host_page".  The "haddr" seems fine.

When preparing the new version, let's also split this diff into
corresponding patches.

Thanks,

> diff --git a/migration/ram.c b/migration/ram.c
> index 734fd90e12..52a3af9245 100644
> --- a/migration/ram.c
> +++ b/migration/ram.c
> @@ -269,7 +269,14 @@ static void ramblock_pending_bmap_init(void)
> 
>      RAMBLOCK_FOREACH_NOT_IGNORED(rb) {
>          assert(!rb->pending_bmap);
> -        size_t size = rb->max_length / qemu_ram_pagesize(rb);
> +        /*
> +         * The pending_bmap granularity must match the maximum of
> host and guest
> +         * page sizes. This ensures that every load operation checks for one
> +         * bit, allowing lockless thread coordination via a single-bit atomic
> +         * test-and-clear.
> +         */
> +        size_t size = rb->max_length /
> +                      MAX(qemu_ram_pagesize(rb), qemu_target_page_size());
>          rb->pending_bmap = bitmap_new(size);
>          bitmap_set(rb->pending_bmap, 0, size);
>      }
> 
> On Sat, Aug 1, 2026 at 8:07 AM Aadeshveer Singh <aadeshveer07@gmail.com> wrote:
> >
> > In fast snapshot load, we would like to serve faults as soon as possible
> > hence loading pages directly instead of requesting a source
> >
> > Add postcopy_mapped_ram_load_page() function which serves single page
> > fault. It uses bitmap_test_and_clear_atomic() on pending_bmap to prevent
> > multiple threads from loading same page. It loads a page or pages
> > depending on size of guest pages and host pages, loading exactly the
> > larger of two. If the entire page is zero postcopy_place_page_zero() is
> > used for efficiency and in case some part is non zero it is part by part
> > loaded on loop using new function postcopy_mapped_ram_load_guest_page()
> > which loads a single guest page in a buffer which is then placed using
> > postcopy_place_page(). This covers all possible cases for various page
> > sizes of host and guest.
> >
> > Update postcopy_ram_fault_thread to call postcopy_mapped_ram_load_page
> > instead of requesting source in case of fast snapshot load. to_src_file
> > check is bypassed in fast snapshot load case as there is no source.
> >
> > Call try_mark_postcopy_blocktime_begin on every page fault to support
> > postcopy-blocktime.
> >
> > Allocate another channel in postcopy_temp_pages_setup(like the preempt
> > case), for both the fault thread and eager thread to load pages
> > independently.
> >
> > Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> > ---
> >  migration/postcopy-ram.c | 174 +++++++++++++++++++++++++++++++++++----
> >  1 file changed, 157 insertions(+), 17 deletions(-)
> >
> > diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
> > index 2e2c9fae10..1da9c4309b 100644
> > --- a/migration/postcopy-ram.c
> > +++ b/migration/postcopy-ram.c
> > @@ -949,6 +949,121 @@ int postcopy_wake_shared(struct PostCopyFD *pcfd,
> >                         pagesize);
> >  }
> >
> > +/*
> > + * Load a single guest page from source file into the buffer.
> > + * NOTE: This is not an atomic operation and should not be used to directly load
> > + * pages on page faults in postcopy. It is meant to fill in buffer that can then
> > + * be copied into the faulting location using UFFDIO_COPY.
> > + */
> > +static bool postcopy_mapped_ram_load_guest_page(MigrationIncomingState *mis,
> > +                                                RAMBlock *rb,
> > +                                                ram_addr_t rb_offset, void *buf,
> > +                                                Error **errp)
> > +{
> > +    ERRP_GUARD();
> > +    size_t page = rb_offset / qemu_target_page_size();
> > +    size_t read;
> > +
> > +    if (test_bit(page, rb->file_bmap)) {
> > +        /*
> > +         * This can happen concurrently, but it's thread-safe because
> > +         * qemu_get_buffer_at() is thread-safe, and the caller will be using
> > +         * different temporary buffers.
> > +         */
> > +        read =
> > +            qemu_get_buffer_at(mis->from_src_file, buf, qemu_target_page_size(),
> > +                               rb->pages_offset + rb_offset, errp);
> > +
> > +        if (read != qemu_target_page_size()) {
> > +            error_prepend(errp,
> > +                          "Could not read page %zu from RAM Block %s: ", page,
> > +                          rb->idstr);
> > +            return false;
> > +        }
> > +    } else {
> > +        memset(buf, '\0', qemu_target_page_size());
> > +    }
> > +    return true;
> > +}
> > +
> > +/**
> > + * postcopy_mapped_ram_load_page() - Load pages required to access host address.
> > + * @mis: Migration Incoming State.
> > + * @rb: RAMBlock from where page is loaded.
> > + * @rb_offset: Offset of target page in RAMBlock.
> > + * @haddr: Base of target page where to load in page.
> > + * @channel: Used to identify between threads and use corresponding temp.
> > + * @errp: Set error in case of failure
> > + *
> > + * Load page(s) from RAMBlock covering the faulting address. We might need to
> > + * load multiple pages in the case when host page size is greater than guest
> > + * page size. As userfaultfd works on granularity of host pages, we might need
> > + * to load guest pages in single operation.
> > + *
> > + * Return: True on success.
> > + */
> > +static bool postcopy_mapped_ram_load_page(MigrationIncomingState *mis,
> > +                                          RAMBlock *rb, ram_addr_t rb_offset,
> > +                                          uint64_t haddr, int channel,
> > +                                          Error **errp)
> > +{
> > +    void *place_source = mis->postcopy_tmp_pages[channel].tmp_huge_page;
> > +    char *buffer_ptr = (char *)place_source;
> > +    size_t guest_pages_to_load =
> > +        MAX(1, qemu_ram_pagesize(rb) / qemu_target_page_size());
> > +    size_t host_page;
> > +    size_t page;
> > +
> > +    /*
> > +     * If guest page size is greater than host page size uffd needs to load one
> > +     * guest page and multiple host pages, hence the offsets need to aligned
> > +     * with guest pages (which is automatically aligned with host pages). In the
> > +     * same case we need to check range of bits on pending_bmap(bit per host
> > +     * page) to decide whether all the page have been loaded
> > +     */
> > +    rb_offset = ROUND_DOWN(rb_offset, qemu_target_page_size());
> > +    haddr = ROUND_DOWN(haddr, qemu_target_page_size());
> > +    host_page = rb_offset / qemu_ram_pagesize(rb);
> > +    page = rb_offset >> qemu_target_page_bits();
> > +
> > +    if (bitmap_test_and_clear_atomic(
> > +            rb->pending_bmap, host_page,
> > +            MAX(1, qemu_target_page_size() / qemu_ram_pagesize(rb)))) {
> > +        if (find_next_bit(rb->file_bmap, page + guest_pages_to_load, page) ==
> > +            page + guest_pages_to_load) {
> > +            /* It is efficient to use UFFDIO_ZERO if all pages are zero */
> > +            if (postcopy_place_page_zero(mis, (void *)haddr, rb)) {
> > +                error_setg(errp,
> > +                           "Failed to place zero page %zu from RAM Block %s at "
> > +                           "address %" PRIu64,
> > +                           page, rb->idstr, haddr);
> > +                return false;
> > +            }
> > +        } else {
> > +            size_t load_size = guest_pages_to_load * qemu_target_page_size();
> > +            size_t offset;
> > +
> > +            for (offset = 0; offset < load_size;
> > +                 offset += qemu_target_page_size()) {
> > +                if (!postcopy_mapped_ram_load_guest_page(
> > +                        mis, rb, rb_offset + offset, buffer_ptr + offset,
> > +                        errp)) {
> > +                    return false;
> > +                }
> > +            }
> > +
> > +            if (postcopy_place_page(mis, (void *)haddr, place_source, rb)) {
> > +                error_setg(errp,
> > +                           "Failed to place page %zu from RAM Block %s at "
> > +                           "address %" PRIu64,
> > +                           page, rb->idstr, haddr);
> > +                return false;
> > +            }
> > +        }
> > +    }
> > +    return true;
> > +}
> > +
> >  /*
> >   * NOTE: @tid is only used when postcopy-blocktime feature is enabled, and
> >   * also optional: when zero is provided, the fault accounting will be ignored.
> > @@ -1310,6 +1425,7 @@ static void *postcopy_ram_fault_thread(void *opaque)
> >      int ret;
> >      size_t index;
> >      RAMBlock *rb = NULL;
> > +    Error *local_err = NULL;
> >
> >      trace_postcopy_ram_fault_thread_entry();
> >      rcu_register_thread();
> > @@ -1351,11 +1467,13 @@ static void *postcopy_ram_fault_thread(void *opaque)
> >              break;
> >          }
> >
> > -        if (!mis->to_src_file) {
> > +        if (!migrate_mapped_ram() && !mis->to_src_file) {
> >              /*
> > -             * Possibly someone tells us that the return path is
> > -             * broken already using the event. We should hold until
> > -             * the channel is rebuilt.
> > +             * Possibly someone tells us that the return path is broken already
> > +             * using the event. We should hold until the channel is rebuilt.
> > +             * Fast snapshot load doesn't support pause and recover, because
> > +             * it's not necessary: we can fail right away when QEMU just booted
> > +             * with nothing to lose.
> >               */
> >              postcopy_pause_fault_thread(mis);
> >          }
> > @@ -1418,18 +1536,37 @@ static void *postcopy_ram_fault_thread(void *opaque)
> >                                                  qemu_ram_get_idstr(rb),
> >                                                  rb_offset,
> >                                                  msg.arg.pagefault.feat.ptid);
> > +
> > +            if (migrate_mapped_ram()) {
> > +                /* Load page directly in case of fast snapshot load */
> > +
> > +                uintptr_t aligned = (uintptr_t)ROUND_DOWN(
> > +                    msg.arg.pagefault.address, qemu_ram_pagesize(rb));
> > +
> > +                if (try_mark_postcopy_blocktime_begin(
> > +                        mis, rb, rb_offset, (uintptr_t)aligned,
> > +                        msg.arg.pagefault.feat.ptid)) {
> > +                    if (!postcopy_mapped_ram_load_page(
> > +                            mis, rb, rb_offset, aligned, RAM_CHANNEL_POSTCOPY,
> > +                            &local_err)) {
> > +                        error_report_err(local_err);
> > +                        break;
> > +                    }
> > +                }
> > +            } else {
> >  retry:
> > -            /*
> > -             * Send the request to the source - we want to request one
> > -             * of our host page sizes (which is >= TPS)
> > -             */
> > -            ret = postcopy_request_page(mis, rb, rb_offset,
> > -                                        msg.arg.pagefault.address,
> > -                                        msg.arg.pagefault.feat.ptid);
> > -            if (ret) {
> > -                /* May be network failure, try to wait for recovery */
> > -                postcopy_pause_fault_thread(mis);
> > -                goto retry;
> > +                /*
> > +                 * Send the request to the source - we want to request one
> > +                 * of our host page sizes (which is >= TPS)
> > +                 */
> > +                ret = postcopy_request_page(mis, rb, rb_offset,
> > +                                            msg.arg.pagefault.address,
> > +                                            msg.arg.pagefault.feat.ptid);
> > +                if (ret) {
> > +                    /* May be network failure, try to wait for recovery */
> > +                    postcopy_pause_fault_thread(mis);
> > +                    goto retry;
> > +                }
> >              }
> >          }
> >
> > @@ -1501,8 +1638,11 @@ static int postcopy_temp_pages_setup(MigrationIncomingState *mis, Error **errp)
> >      unsigned i, channels;
> >      void *temp_page;
> >
> > -    if (migrate_postcopy_preempt()) {
> > -        /* If preemption enabled, need extra channel for urgent requests */
> > +    if (migrate_postcopy_preempt() || migrate_mapped_ram()) {
> > +        /*
> > +         * If preemption enabled or it is fast snapshot load, need extra channel
> > +         * for urgent requests/faults
> > +         */
> >          mis->postcopy_channels = RAM_CHANNEL_MAX;
> >      } else {
> >          /* Both precopy/postcopy on the same channel */
> > --
> > 2.55.0
> >
> 

-- 
Peter Xu



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

* Re: [PATCH v4 08/11] migration: add eager load thread and setup for fast snapshot load
  2026-08-01  2:36 ` [PATCH v4 08/11] migration: add eager load thread and setup for fast snapshot load Aadeshveer Singh
@ 2026-08-10 15:48   ` Juraj Marcin
  0 siblings, 0 replies; 27+ messages in thread
From: Juraj Marcin @ 2026-08-10 15:48 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On 2026-08-01 08:06, Aadeshveer Singh wrote:
> In fast snapshot load a thread is needed for actively loading in pages
> along with the fault path so that the guest is not dependent on fault
> thread indefinitely. Considering the difference from usual network
> postcopy where major chunk of RAM is already loaded here entire RAM
> needs to be loaded later. Existance of background pages which are not
> really accessed by the guest might never be loaded and system will be
> locked in migration for indefinite time. As there should be no
> assumption about how guest accesses memory, the load times can be
> indefinite.
> 
> Add postcopy_ram_eager_load_thread(), for the eager thread which
> iterates over all non ignored blocks calling ram_block_load_eager()
> on each. ram_block_load_eager then iterates to load in all pages using
> postcopy_mapped_ram_load_page(), with a different channel, which takes
> care of not loading in pages already loaded by fault thread. On
> completion the thread schedules postcopy_incoming_complete_bh() to
> destroy the incoming migration state.
> 
> Add postcopy_ram_eager_load_setup() to create the thread. Added joining
> logic in postcopy_incoming_cleanup().
> 
> Add tracepoints for entry and exit to eager load thread.
> 
> When both mapped-ram and postcopy-ram are set, divert from
> qemu_loadvm_state to run fast snapshot load
> 
> Initialize postcopy RAM state and register RAM Blocks with userfaultfd
> via ram_postcopy_incoming_init() and postcopy_ram_incoming_setup() in
> process_incoming_migration_co(). Fault thread needs to be launched
> before VM to serve faults for some hardwares emulation that need to read
> RAM (like vapic devices). Populate bitmaps and offset tables while
> reading file in qemu_loadvm_state_main.
> 
> Add function qemu_loadvm_run_fast_snapshot_load() which starts the VM
> using loadvm_postcopy_handle_run_bh() and launches eager load thread.
> 
> Skip scheduling process_incoming_migration_bh() in
> process_incoming_migration_co(), for fast snapshot load as the state
> cleanup is managed by eager load thread on completion.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> Reviewed-by: Peter Xu <peterx@redhat.com>
> ---
>  migration/migration.c    | 36 +++++++++++++++++++--
>  migration/migration.h    |  5 +++
>  migration/postcopy-ram.c | 69 ++++++++++++++++++++++++++++++++++++++++
>  migration/postcopy-ram.h |  2 ++
>  migration/savevm.c       | 16 ++++++++++
>  migration/savevm.h       |  2 ++
>  migration/trace-events   |  2 ++
>  7 files changed, 130 insertions(+), 2 deletions(-)

Reviewed-by: Juraj Marcin <jmarcin@redhat.com>



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

* Re: [PATCH v4 09/11] migration: update capability conflict test for postcopy-ram+mapped-ram
  2026-08-01  2:36 ` [PATCH v4 09/11] migration: update capability conflict test for postcopy-ram+mapped-ram Aadeshveer Singh
@ 2026-08-10 15:49   ` Juraj Marcin
  2026-08-10 18:23   ` Peter Xu
  1 sibling, 0 replies; 27+ messages in thread
From: Juraj Marcin @ 2026-08-10 15:49 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On 2026-08-01 08:06, Aadeshveer Singh wrote:
> Remove the test test_validate_caps_pair, which asserted postcopy-ram
> and mapped-ram capabilities cannot be active together. The new fast
> snapshot load feature is exactly this pair of capabilities active
> together, with the following patches in this series, this combination
> will now supported and be functional.
> 
> Remove the capability check that rejected mapped-ram and postcopy-ram
> being set simultaneously, as this combination now corresponds to fast
> snapshot load.
> 
> Add a capability check against setting multifd when configured for fast
> snapshot load as it is not supported yet. Also add capability check
> against setting postcopy-preempt as it is incompatible.
> 
> Add infrastructure to check postcopy_notifier_list being empty in
> capaility checking to block vhost-user with fast snapshot load as it is
> not supported.
> 
> A smoke test exercising this feature has been added further in this
> series.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> ---
>  include/qemu/notify.h              |  2 ++
>  migration/options.c                | 20 ++++++++++--
>  migration/postcopy-ram.c           |  5 +++
>  migration/postcopy-ram.h           |  1 +
>  tests/qtest/migration/misc-tests.c | 52 ------------------------------
>  util/notify.c                      |  5 +++
>  6 files changed, 31 insertions(+), 54 deletions(-)

Reviewed-by: Juraj Marcin <jmarcin@redhat.com>



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

* Re: [PATCH v4 10/11] migration/tests: Add test for fast snapshot load
  2026-08-01  2:36 ` [PATCH v4 10/11] migration/tests: Add test for fast snapshot load Aadeshveer Singh
@ 2026-08-10 15:50   ` Juraj Marcin
  0 siblings, 0 replies; 27+ messages in thread
From: Juraj Marcin @ 2026-08-10 15:50 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On 2026-08-01 08:06, Aadeshveer Singh wrote:
> Add test_postcopy_file_mapped_ram() to test for saving a snapshot in
> mapped ram format and loading it using postcopy/fast snapshot load.
> 
> Test is similar to test_precopy_file_mapped_ram() with additional
> start_hook to set postcopy capabilities on the receiving end.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> Reviewed-by: Peter Xu <peterx@redhat.com>
> ---
>  tests/qtest/migration/file-tests.c | 24 ++++++++++++++++++++++++
>  1 file changed, 24 insertions(+)

Reviewed-by: Juraj Marcin <jmarcin@redhat.com>



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

* Re: [PATCH v4 09/11] migration: update capability conflict test for postcopy-ram+mapped-ram
  2026-08-01  2:36 ` [PATCH v4 09/11] migration: update capability conflict test for postcopy-ram+mapped-ram Aadeshveer Singh
  2026-08-10 15:49   ` Juraj Marcin
@ 2026-08-10 18:23   ` Peter Xu
  1 sibling, 0 replies; 27+ messages in thread
From: Peter Xu @ 2026-08-10 18:23 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On Sat, Aug 01, 2026 at 08:06:26AM +0530, Aadeshveer Singh wrote:
> Remove the test test_validate_caps_pair, which asserted postcopy-ram
> and mapped-ram capabilities cannot be active together. The new fast
> snapshot load feature is exactly this pair of capabilities active
> together, with the following patches in this series, this combination
> will now supported and be functional.
> 
> Remove the capability check that rejected mapped-ram and postcopy-ram
> being set simultaneously, as this combination now corresponds to fast
> snapshot load.
> 
> Add a capability check against setting multifd when configured for fast
> snapshot load as it is not supported yet. Also add capability check
> against setting postcopy-preempt as it is incompatible.
> 
> Add infrastructure to check postcopy_notifier_list being empty in
> capaility checking to block vhost-user with fast snapshot load as it is
> not supported.
> 
> A smoke test exercising this feature has been added further in this
> series.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>

Reviewed-by: Peter Xu <peterx@redhat.com>

-- 
Peter Xu



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

* Re: [PATCH v4 11/11] docs/migration: Add documentation for fast snapshot load feature
  2026-08-01  2:36 ` [PATCH v4 11/11] docs/migration: Add documentation for fast snapshot load feature Aadeshveer Singh
@ 2026-08-10 18:24   ` Peter Xu
  0 siblings, 0 replies; 27+ messages in thread
From: Peter Xu @ 2026-08-10 18:24 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On Sat, Aug 01, 2026 at 08:06:28AM +0530, Aadeshveer Singh wrote:
> Add documenatation for fast snapshot load covering an overview, the
> architecture, usage and limitations.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>

Reviewed-by: Peter Xu <peterx@redhat.com>

-- 
Peter Xu



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

* Re: [PATCH v4 00/11] migration: fast snapshot load
  2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
                   ` (10 preceding siblings ...)
  2026-08-01  2:36 ` [PATCH v4 11/11] docs/migration: Add documentation for fast snapshot load feature Aadeshveer Singh
@ 2026-08-10 19:02 ` Peter Xu
  11 siblings, 0 replies; 27+ messages in thread
From: Peter Xu @ 2026-08-10 19:02 UTC (permalink / raw)
  To: Aadeshveer Singh
  Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub,
	pierrick.bouvier

On Sat, Aug 01, 2026 at 08:06:17AM +0530, Aadeshveer Singh wrote:
> This series implements a "fast snapshot load" mechanism to
> significantly reduce the perceived resume time of a VM from a snapshot
> file.

I gave this one a quick shot, it ran all fine here.  It's a bit of a pity
that I still almost need to rely on postcopy-blocktime to get some more
info out of the async load process.. but I think it's still OK.

One other issue I found though, after applying this patch, QEMU will also
allow me to use QMP/HMP "migrate" (rather than "migrate_incoming") command
when both postcopy and mapped-ram are enabled, which will ultimately leads
to an error:

(qemu) info migrate                                                         
Status:                 failed (Unable to read from file: Bad file descriptor)

What we can do is to disable it for qmp_migrate(), e.g.  migrate_prepare()
can bail out for outgoing migrations of such setup.

Other than that (and the page size issue I raised in the separate email),
I think it's ready.

Thanks,

-- 
Peter Xu



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

end of thread, other threads:[~2026-08-10 19:02 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-01  2:36 [PATCH v4 00/11] migration: fast snapshot load Aadeshveer Singh
2026-08-01  2:36 ` [PATCH v4 01/11] migration: Propagate error in postcopy setup functions Aadeshveer Singh
2026-08-10 15:08   ` Juraj Marcin
2026-08-01  2:36 ` [PATCH v4 02/11] migration: Extract blocktime marking helper Aadeshveer Singh
2026-08-10 15:08   ` Juraj Marcin
2026-08-01  2:36 ` [PATCH v4 03/11] migration: Rename postcopy_listen_thread_bh Aadeshveer Singh
2026-08-10 15:09   ` Juraj Marcin
2026-08-01  2:36 ` [PATCH v4 04/11] migration: Use file_bmap for RAMBlock during incoming file load Aadeshveer Singh
2026-08-10 15:09   ` Juraj Marcin
2026-08-01  2:36 ` [PATCH v4 05/11] migration: Make qemu_get_buffer_at() thread-safe Aadeshveer Singh
2026-08-10 14:14   ` Peter Xu
2026-08-10 15:10   ` Juraj Marcin
2026-08-01  2:36 ` [PATCH v4 06/11] migration: add RAMBlock field and helper for fast snapshot load Aadeshveer Singh
2026-08-10 15:12   ` Juraj Marcin
2026-08-01  2:36 ` [PATCH v4 07/11] migration: add support for fault thread to load pages from disk Aadeshveer Singh
2026-08-08  4:34   ` Aadeshveer Singh
2026-08-10 15:40     ` Peter Xu
2026-08-01  2:36 ` [PATCH v4 08/11] migration: add eager load thread and setup for fast snapshot load Aadeshveer Singh
2026-08-10 15:48   ` Juraj Marcin
2026-08-01  2:36 ` [PATCH v4 09/11] migration: update capability conflict test for postcopy-ram+mapped-ram Aadeshveer Singh
2026-08-10 15:49   ` Juraj Marcin
2026-08-10 18:23   ` Peter Xu
2026-08-01  2:36 ` [PATCH v4 10/11] migration/tests: Add test for fast snapshot load Aadeshveer Singh
2026-08-10 15:50   ` Juraj Marcin
2026-08-01  2:36 ` [PATCH v4 11/11] docs/migration: Add documentation for fast snapshot load feature Aadeshveer Singh
2026-08-10 18:24   ` Peter Xu
2026-08-10 19:02 ` [PATCH v4 00/11] migration: fast snapshot load Peter Xu

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.