All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v2 0/7] migration: fast snapshot load
@ 2026-06-30  8:19 Aadeshveer Singh
  2026-06-30  8:19 ` [RFC PATCH v2 1/7] migration/tests: remove capability conflict test postcopy-ram+mapped-ram Aadeshveer Singh
                   ` (7 more replies)
  0 siblings, 8 replies; 16+ messages in thread
From: Aadeshveer Singh @ 2026-06-30  8:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	Aadeshveer Singh

This RFC 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 was tested using a Debian 13 bare minimum system and Fedora
44 KDE, snapshots for both are loaded successfully with no error.

Next Steps:
- Add testing framework, in qtest and unit tests
- Update documentation

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

---
v1 -> v2

- Structure Change: Expanded from 5 to 7 patches
- Move test removal patch from last to first as suggested by Peter
- Patch 2 to 4 are meant to be non functional changes meant to prepare
  for actual feature Implementation
- Patch 3 and 4 from last series were squashed together into the
  final patch
- Improve error handling in postcopy_ram_incoming_setup() and
  postcopy_temp_pages_setup() for easier error handling
- Use pending_bmap for loading eliminating need of nonzeropages map
  as suggested by Peter
- Keep exit from qemu_get_buffer_at() in case the file has error as
  pointed by Peter
- Eliminate migrate_fast_snapshot_load() to prevent complication of
  capability matrix as pointed by Fabiano
- Add function ram_should_load_postcopy_pages() to improve readability
  as suggested by Peter
- Fixed comment for postcopy_mapped_ram_load_page() to align with
  auto documentation as pointed by peter
- Modify postcopy_mapped_ram_load_page() to return boolean for success
  and use errp for modular error handling
- Replaced use of TARGET_PAGE_SIZE with qemu_ram_pagesize(rb) for
  modularity and ease of adding hugepages support
- Add blocktime support directly and fixed a race condition on
  received map
- Add migration_incoming_has_postcopy_thread() to improve readability
  as suggested by Peter
- Moved the setup and run logic for fast snapshot from
  qemu_loadvm_state() to process_incoming_migration_co() for cleanliness
  as suggested by Peter
- Rename eager load thread to snapshot_load as suggested by Peter
- Use RAM_CHANNEL_PRECOPY/RAM_CHANNEL_POSTCOPY instead of hardcoded
  values for eager thread and fault thread load channels as suggested
  by Peter
- Reused postcopy_listen_thread_bh(and rename it to
  postcopy_incoming_complete_bh) as suggested by Peter
- Removed the bypass on process_incoming_migration_bh scheduling as
  pointed by Peter and Fabiano

Aadeshveer Singh (7):
  migration/tests: remove capability conflict test
    postcopy-ram+mapped-ram
  migration: Propagate error in postcopy setup functions
  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

 include/system/ramblock.h          |   6 +
 migration/migration.c              |  33 +++-
 migration/migration.h              |   5 +
 migration/options.c                |   6 -
 migration/postcopy-ram.c           | 236 +++++++++++++++++++++++------
 migration/postcopy-ram.h           |   4 +-
 migration/qemu-file.c              |   5 +-
 migration/ram.c                    |  88 +++++++++--
 migration/savevm.c                 |  26 ++++
 migration/savevm.h                 |   2 +
 migration/trace-events             |   2 +
 tests/qtest/migration/misc-tests.c |  52 -------
 12 files changed, 347 insertions(+), 118 deletions(-)

-- 
2.54.0



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

* [RFC PATCH v2 1/7] migration/tests: remove capability conflict test postcopy-ram+mapped-ram
  2026-06-30  8:19 [RFC PATCH v2 0/7] migration: fast snapshot load Aadeshveer Singh
@ 2026-06-30  8:19 ` Aadeshveer Singh
  2026-07-06 19:02   ` Peter Xu
  2026-06-30  8:19 ` [RFC PATCH v2 2/7] migration: Propagate error in postcopy setup functions Aadeshveer Singh
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 16+ messages in thread
From: Aadeshveer Singh @ 2026-06-30  8:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	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.

No replacement test for fast snapshot load is included in this RFC. A
test exercising the full save/load flow will be added in a follow-up.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
---
 migration/options.c                |  6 ----
 tests/qtest/migration/misc-tests.c | 52 ------------------------------
 2 files changed, 58 deletions(-)

diff --git a/migration/options.c b/migration/options.c
index 5cbfd29099..16583651ac 100644
--- a/migration/options.c
+++ b/migration/options.c
@@ -727,12 +727,6 @@ 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_POSTCOPY_RAM]) {
-            error_setg(errp,
-                       "Mapped-ram migration is incompatible with postcopy");
-            return false;
-        }
     }
 
     /*
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);
 }
-- 
2.54.0



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

* [RFC PATCH v2 2/7] migration: Propagate error in postcopy setup functions
  2026-06-30  8:19 [RFC PATCH v2 0/7] migration: fast snapshot load Aadeshveer Singh
  2026-06-30  8:19 ` [RFC PATCH v2 1/7] migration/tests: remove capability conflict test postcopy-ram+mapped-ram Aadeshveer Singh
@ 2026-06-30  8:19 ` Aadeshveer Singh
  2026-07-06 19:13   ` Peter Xu
  2026-06-30  8:19 ` [RFC PATCH v2 3/7] migration: Use file_bmap for RAMBlock during incoming file load Aadeshveer Singh
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 16+ messages in thread
From: Aadeshveer Singh @ 2026-06-30  8:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	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>
---
 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.54.0



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

* [RFC PATCH v2 3/7] migration: Use file_bmap for RAMBlock during incoming file load
  2026-06-30  8:19 [RFC PATCH v2 0/7] migration: fast snapshot load Aadeshveer Singh
  2026-06-30  8:19 ` [RFC PATCH v2 1/7] migration/tests: remove capability conflict test postcopy-ram+mapped-ram Aadeshveer Singh
  2026-06-30  8:19 ` [RFC PATCH v2 2/7] migration: Propagate error in postcopy setup functions Aadeshveer Singh
@ 2026-06-30  8:19 ` Aadeshveer Singh
  2026-07-06 19:29   ` Peter Xu
  2026-06-30  8:19 ` [RFC PATCH v2 4/7] migration: Make qemu_get_buffer_at() thread-safe Aadeshveer Singh
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 16+ messages in thread
From: Aadeshveer Singh @ 2026-06-30  8:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	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>
---
 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.54.0



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

* [RFC PATCH v2 4/7] migration: Make qemu_get_buffer_at() thread-safe
  2026-06-30  8:19 [RFC PATCH v2 0/7] migration: fast snapshot load Aadeshveer Singh
                   ` (2 preceding siblings ...)
  2026-06-30  8:19 ` [RFC PATCH v2 3/7] migration: Use file_bmap for RAMBlock during incoming file load Aadeshveer Singh
@ 2026-06-30  8:19 ` Aadeshveer Singh
  2026-07-06 19:34   ` Peter Xu
  2026-06-30  8:19 ` [RFC PATCH v2 5/7] migration: add RAMBlock field and helper for fast snapshot load Aadeshveer Singh
                   ` (3 subsequent siblings)
  7 siblings, 1 reply; 16+ messages in thread
From: Aadeshveer Singh @ 2026-06-30  8:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	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.

Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
---
 migration/qemu-file.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/migration/qemu-file.c b/migration/qemu-file.c
index d5a48115bd..c73f8178d7 100644
--- a/migration/qemu-file.c
+++ b/migration/qemu-file.c
@@ -553,14 +553,11 @@ 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)
 {
-    Error *err = NULL;
-
     if (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, NULL) < 0) {
         return 0;
     }
 
-- 
2.54.0



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

* [RFC PATCH v2 5/7] migration: add RAMBlock field and helper for fast snapshot load
  2026-06-30  8:19 [RFC PATCH v2 0/7] migration: fast snapshot load Aadeshveer Singh
                   ` (3 preceding siblings ...)
  2026-06-30  8:19 ` [RFC PATCH v2 4/7] migration: Make qemu_get_buffer_at() thread-safe Aadeshveer Singh
@ 2026-06-30  8:19 ` Aadeshveer Singh
  2026-07-06 20:03   ` Peter Xu
  2026-06-30  8:19 ` [RFC PATCH v2 6/7] migration: add support for fault thread to load pages from disk Aadeshveer Singh
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 16+ messages in thread
From: Aadeshveer Singh @ 2026-06-30  8:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	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 4728f14d73..ffd60b2ac8 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_target_page_bits();
+        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.54.0



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

* [RFC PATCH v2 6/7] migration: add support for fault thread to load pages from disk
  2026-06-30  8:19 [RFC PATCH v2 0/7] migration: fast snapshot load Aadeshveer Singh
                   ` (4 preceding siblings ...)
  2026-06-30  8:19 ` [RFC PATCH v2 5/7] migration: add RAMBlock field and helper for fast snapshot load Aadeshveer Singh
@ 2026-06-30  8:19 ` Aadeshveer Singh
  2026-07-06 20:35   ` Peter Xu
  2026-06-30  8:19 ` [RFC PATCH v2 7/7] migration: add eager load thread and setup for fast snapshot load Aadeshveer Singh
  2026-07-06 19:16 ` [RFC PATCH v2 0/7] migration: " Peter Xu
  7 siblings, 1 reply; 16+ messages in thread
From: Aadeshveer Singh @ 2026-06-30  8:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	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 by reading the snapshot file. It uses bitmap_test_and_clear_atomic
on pending_bmap to coordinate between threads so each page is loaded
exactly once. Non-zero pages are read using qemu_get_buffer_at into a
temporary page (for loading page atomically), which is then placed using
postcopy_place_page. Zero pages are placed directly using
postcopy_place_page_zero.

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.

Add postcopy-blocktime support by calling mark_postcopy_blocktime_begin
on every fault. We need to aqcuire the page_request_mutex lock and check
the recv bitmap to make sure we prevent a race condition when a fault
occurs but before the fault thread can mark the page for tracking the
eager thread loads in the page making us fail the assert for recv bitmap
being clear while marking in mark_postcopy_blocktime_begin().

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 | 121 +++++++++++++++++++++++++++++++++------
 1 file changed, 104 insertions(+), 17 deletions(-)

diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
index 96a65aa976..be9edac572 100644
--- a/migration/postcopy-ram.c
+++ b/migration/postcopy-ram.c
@@ -949,6 +949,68 @@ int postcopy_wake_shared(struct PostCopyFD *pcfd,
                        pagesize);
 }
 
+/**
+ * postcopy_mapped_ram_load_page() - Load a page to given host address.
+ * @mis: Migration Incoming State.
+ * @rb: RAMBlock from where page is loaded.
+ * @rb_offset: Offset of page in RAMBlock.
+ * @haddr: Base of page where to load in page.
+ * @channel: Used to identify between threads and use corresponding temp.
+ *
+ * Load a page from RAMBlock at offset at given host address. Used by postcopy
+ * ram fault thread and eager thread in fast snapshot load case.
+ *
+ * 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;
+    size_t page;
+    size_t read;
+
+    page = rb_offset / qemu_ram_pagesize(rb);
+
+    if (bitmap_test_and_clear_atomic(rb->pending_bmap, page, 1)) {
+        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, place_source,
+                                      qemu_ram_pagesize(rb),
+                                      rb->pages_offset + rb_offset);
+
+            if (read != qemu_ram_pagesize(rb)) {
+                error_setg(errp, "Could not read page %zu from RAM Block %s",
+                           page, rb->idstr);
+                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;
+            }
+
+        } else {
+            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;
+            }
+        }
+    }
+    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.
@@ -1279,6 +1341,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();
@@ -1320,11 +1383,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);
         }
@@ -1387,18 +1452,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 */
+                WITH_QEMU_LOCK_GUARD(&mis->page_request_mutex) {
+                    if (!ramblock_recv_bitmap_test(
+                            rb, (void *)msg.arg.pagefault.address)) {
+                        mark_postcopy_blocktime_begin(
+                            msg.arg.pagefault.address,
+                            msg.arg.pagefault.feat.ptid, rb);
+                    }
+                }
+                if (!postcopy_mapped_ram_load_page(
+                        mis, rb, rb_offset, msg.arg.pagefault.address,
+                        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;
+                }
             }
         }
 
@@ -1470,8 +1554,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.54.0



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

* [RFC PATCH v2 7/7] migration: add eager load thread and setup for fast snapshot load
  2026-06-30  8:19 [RFC PATCH v2 0/7] migration: fast snapshot load Aadeshveer Singh
                   ` (5 preceding siblings ...)
  2026-06-30  8:19 ` [RFC PATCH v2 6/7] migration: add support for fault thread to load pages from disk Aadeshveer Singh
@ 2026-06-30  8:19 ` Aadeshveer Singh
  2026-07-06 21:07   ` Peter Xu
  2026-07-06 19:16 ` [RFC PATCH v2 0/7] migration: " Peter Xu
  7 siblings, 1 reply; 16+ messages in thread
From: Aadeshveer Singh @ 2026-06-30  8:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: peterx, farosas, pbonzini, philmd, lvivier, ayoub,
	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>
---
 migration/migration.c    | 33 ++++++++++++++++--
 migration/migration.h    |  5 +++
 migration/postcopy-ram.c | 74 ++++++++++++++++++++++++++++++++++++++--
 migration/postcopy-ram.h |  2 ++
 migration/savevm.c       | 26 ++++++++++++++
 migration/savevm.h       |  2 ++
 migration/trace-events   |  2 ++
 7 files changed, 140 insertions(+), 4 deletions(-)

diff --git a/migration/migration.c b/migration/migration.c
index 074d3f2c69..9295794713 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -730,6 +730,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)
 {
@@ -759,17 +764,41 @@ process_incoming_migration_co(void *opaque)
     migrate_set_state(&mis->state, MIGRATION_STATUS_SETUP,
                       MIGRATION_STATUS_ACTIVE);
 
+    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()) {
+        if (qemu_loadvm_run_fast_snapshot_load(mis->from_src_file, mis,
+                                               &local_err)) {
+            goto fail;
+        }
+    }
 
     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 be9edac572..c31abd4339 100644
--- a/migration/postcopy-ram.c
+++ b/migration/postcopy-ram.c
@@ -2159,7 +2159,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();
@@ -2267,7 +2267,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));
 
@@ -2311,9 +2311,79 @@ 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;
+
+    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)) {
+        migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
+                          MIGRATION_STATUS_FAILED);
+        goto out;
+    }
+    migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
+                      MIGRATION_STATUS_COMPLETED);
+
+out:
+    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
+ */
+int 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;
+    return 0;
+}
diff --git a/migration/postcopy-ram.h b/migration/postcopy-ram.h
index 98d918713f..6d1a296795 100644
--- a/migration/postcopy-ram.h
+++ b/migration/postcopy-ram.h
@@ -202,4 +202,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);
 
+int postcopy_ram_eager_load_setup(MigrationIncomingState *mis);
+
 #endif
diff --git a/migration/savevm.c b/migration/savevm.c
index 23adaf9dd9..5857fb8ac2 100644
--- a/migration/savevm.c
+++ b/migration/savevm.c
@@ -2959,6 +2959,32 @@ static bool postcopy_pause_incoming(MigrationIncomingState *mis)
     return true;
 }
 
+/*
+ * Starts the VM and launches the eager thread for fast snapshot load
+ */
+int qemu_loadvm_run_fast_snapshot_load(QEMUFile *f, MigrationIncomingState *mis,
+                                       Error **errp)
+{
+    ERRP_GUARD();
+    int ret = 0;
+
+    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);
+
+    ret = postcopy_ram_eager_load_setup(mis);
+    if (ret) {
+        error_prepend(errp,
+                      "Failed to setup eager load for fast snapshot load: ");
+        return ret;
+    }
+
+    return ret;
+}
+
 int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
                            Error **errp)
 {
diff --git a/migration/savevm.h b/migration/savevm.h
index 96fdf96d4e..f17d7057e1 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);
+int qemu_loadvm_run_fast_snapshot_load(QEMUFile *f, MigrationIncomingState *mis,
+                                       Error **errp);
 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.54.0



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

* Re: [RFC PATCH v2 1/7] migration/tests: remove capability conflict test postcopy-ram+mapped-ram
  2026-06-30  8:19 ` [RFC PATCH v2 1/7] migration/tests: remove capability conflict test postcopy-ram+mapped-ram Aadeshveer Singh
@ 2026-07-06 19:02   ` Peter Xu
  0 siblings, 0 replies; 16+ messages in thread
From: Peter Xu @ 2026-07-06 19:02 UTC (permalink / raw)
  To: Aadeshveer Singh; +Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub

On Tue, Jun 30, 2026 at 01:49:34PM +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.
> 
> No replacement test for fast snapshot load is included in this RFC. A
> test exercising the full save/load flow will be added in a follow-up.

Don't forget to drop this paragraph when reposting.

> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>

Let's move this patch to the end, because it enables everything.  The patch
itself is all fine, thanks.

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

-- 
Peter Xu



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

* Re: [RFC PATCH v2 2/7] migration: Propagate error in postcopy setup functions
  2026-06-30  8:19 ` [RFC PATCH v2 2/7] migration: Propagate error in postcopy setup functions Aadeshveer Singh
@ 2026-07-06 19:13   ` Peter Xu
  0 siblings, 0 replies; 16+ messages in thread
From: Peter Xu @ 2026-07-06 19:13 UTC (permalink / raw)
  To: Aadeshveer Singh; +Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub

On Tue, Jun 30, 2026 at 01:49:35PM +0530, 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>

-- 
Peter Xu



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

* Re: [RFC PATCH v2 0/7] migration: fast snapshot load
  2026-06-30  8:19 [RFC PATCH v2 0/7] migration: fast snapshot load Aadeshveer Singh
                   ` (6 preceding siblings ...)
  2026-06-30  8:19 ` [RFC PATCH v2 7/7] migration: add eager load thread and setup for fast snapshot load Aadeshveer Singh
@ 2026-07-06 19:16 ` Peter Xu
  7 siblings, 0 replies; 16+ messages in thread
From: Peter Xu @ 2026-07-06 19:16 UTC (permalink / raw)
  To: Aadeshveer Singh; +Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub

On Tue, Jun 30, 2026 at 01:49:33PM +0530, Aadeshveer Singh wrote:
> Future direction:
> - Add support for hugepages
> - Add support for multifd
> - Add support for vhost-user

When send the formal patchset, you can add one more patch before the
testing changes, but after the rest, to explicitly fail the migration when
any of above was detected (e.g. update migrate_caps_check).

It's just to make sure when your feature enabled (but with some unsupported
setup) it won't crash QEMU but instead gracefully fail migrate QMP command
(or migrate-set-parameters / migrate-set-capabilities).

Thanks,

-- 
Peter Xu



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

* Re: [RFC PATCH v2 3/7] migration: Use file_bmap for RAMBlock during incoming file load
  2026-06-30  8:19 ` [RFC PATCH v2 3/7] migration: Use file_bmap for RAMBlock during incoming file load Aadeshveer Singh
@ 2026-07-06 19:29   ` Peter Xu
  0 siblings, 0 replies; 16+ messages in thread
From: Peter Xu @ 2026-07-06 19:29 UTC (permalink / raw)
  To: Aadeshveer Singh; +Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub

On Tue, Jun 30, 2026 at 01:49:36PM +0530, 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>
> ---
>  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();

Hmm, I just notice mapped-ram always use guest psize for its bitmap.. even
for huge pages.  In that case this is correct.

> +        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);

Here I think it would still be good to check the page_size is the same as
what was expected (in this case, guest page size), otherwise we should fail
because then the bitmap_size may still be unpredictable and it can cause
illegal access (beyond the size of bitmap allocated) in worst case.

Now I start to wonder when you're working on huge pages with the feature,
did you stick with the bmap having guest page size, or did you modify it to
be host page size?

Thanks,

>  
> -    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.54.0
> 

-- 
Peter Xu



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

* Re: [RFC PATCH v2 4/7] migration: Make qemu_get_buffer_at() thread-safe
  2026-06-30  8:19 ` [RFC PATCH v2 4/7] migration: Make qemu_get_buffer_at() thread-safe Aadeshveer Singh
@ 2026-07-06 19:34   ` Peter Xu
  0 siblings, 0 replies; 16+ messages in thread
From: Peter Xu @ 2026-07-06 19:34 UTC (permalink / raw)
  To: Aadeshveer Singh; +Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub

On Tue, Jun 30, 2026 at 01:49:37PM +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.
> 
> Signed-off-by: Aadeshveer Singh <aadeshveer07@gmail.com>
> ---
>  migration/qemu-file.c | 5 +----
>  1 file changed, 1 insertion(+), 4 deletions(-)
> 
> diff --git a/migration/qemu-file.c b/migration/qemu-file.c
> index d5a48115bd..c73f8178d7 100644
> --- a/migration/qemu-file.c
> +++ b/migration/qemu-file.c
> @@ -553,14 +553,11 @@ 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)
>  {
> -    Error *err = NULL;
> -
>      if (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, NULL) < 0) {
>          return 0;
>      }

This is better from thread safe pov by removing the function that isn't
thread safe, but it goes backward too by dropping the &err to capture the
verbose error.

I think a better way to do this is having Error** passed into
qemu_get_buffer_at(), to get both thread safety and verbose errors.

I should have suggested that last time, sorry.

>  
> -- 
> 2.54.0
> 

-- 
Peter Xu



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

* Re: [RFC PATCH v2 5/7] migration: add RAMBlock field and helper for fast snapshot load
  2026-06-30  8:19 ` [RFC PATCH v2 5/7] migration: add RAMBlock field and helper for fast snapshot load Aadeshveer Singh
@ 2026-07-06 20:03   ` Peter Xu
  0 siblings, 0 replies; 16+ messages in thread
From: Peter Xu @ 2026-07-06 20:03 UTC (permalink / raw)
  To: Aadeshveer Singh; +Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub

On Tue, Jun 30, 2026 at 01:49:38PM +0530, 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(-)
> 
> 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 4728f14d73..ffd60b2ac8 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_target_page_bits();

IIUC we should always stick with rb->page_size (aka, host page size) here
rather than guest page size.  It's because you want to use that one bit to
serialize the two threads (fault thread, eager thread) when do atomic
test_and_clear, and since UFFDIO_COPY needs to happen at host page size
granule, this needs to match that.

> +        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.54.0
> 

-- 
Peter Xu



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

* Re: [RFC PATCH v2 6/7] migration: add support for fault thread to load pages from disk
  2026-06-30  8:19 ` [RFC PATCH v2 6/7] migration: add support for fault thread to load pages from disk Aadeshveer Singh
@ 2026-07-06 20:35   ` Peter Xu
  0 siblings, 0 replies; 16+ messages in thread
From: Peter Xu @ 2026-07-06 20:35 UTC (permalink / raw)
  To: Aadeshveer Singh; +Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub

On Tue, Jun 30, 2026 at 01:49:39PM +0530, Aadeshveer Singh 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 by reading the snapshot file. It uses bitmap_test_and_clear_atomic
> on pending_bmap to coordinate between threads so each page is loaded
> exactly once. Non-zero pages are read using qemu_get_buffer_at into a
> temporary page (for loading page atomically), which is then placed using
> postcopy_place_page. Zero pages are placed directly using
> postcopy_place_page_zero.
> 
> 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.
> 
> Add postcopy-blocktime support by calling mark_postcopy_blocktime_begin
> on every fault. We need to aqcuire the page_request_mutex lock and check
> the recv bitmap to make sure we prevent a race condition when a fault
> occurs but before the fault thread can mark the page for tracking the
> eager thread loads in the page making us fail the assert for recv bitmap
> being clear while marking in mark_postcopy_blocktime_begin().
> 
> 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 | 121 +++++++++++++++++++++++++++++++++------
>  1 file changed, 104 insertions(+), 17 deletions(-)
> 
> diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
> index 96a65aa976..be9edac572 100644
> --- a/migration/postcopy-ram.c
> +++ b/migration/postcopy-ram.c
> @@ -949,6 +949,68 @@ int postcopy_wake_shared(struct PostCopyFD *pcfd,
>                         pagesize);
>  }
>  
> +/**
> + * postcopy_mapped_ram_load_page() - Load a page to given host address.
> + * @mis: Migration Incoming State.
> + * @rb: RAMBlock from where page is loaded.
> + * @rb_offset: Offset of page in RAMBlock.
> + * @haddr: Base of page where to load in page.
> + * @channel: Used to identify between threads and use corresponding temp.

Missing @errp.

> + *
> + * Load a page from RAMBlock at offset at given host address. Used by postcopy
> + * ram fault thread and eager thread in fast snapshot load case.
> + *
> + * 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;
> +    size_t page;
> +    size_t read;
> +
> +    page = rb_offset / qemu_ram_pagesize(rb);

Yes, here it is correct to use qemu_ram_pagesize().

> +
> +    if (bitmap_test_and_clear_atomic(rb->pending_bmap, page, 1)) {
> +        if (test_bit(page, rb->file_bmap)) {

We'll need to be careful here when you add huge page support, because
file_bmap so far is guest-psize based.  This line will start to break for
huge pages, I am not sure if this is the bug you hit when developing huge
page support, maybe yes?

Not sure how you resolved it there if this is the case, but one idea is
when postcopy-ram is enabled, you can convert the per-guest-psize bitmap
into per-host-psize bitmap (when any bit set within the huge page range,
set the bit in the new bitmap), then here it will be correct.

> +            /*
> +             * 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, place_source,
> +                                      qemu_ram_pagesize(rb),
> +                                      rb->pages_offset + rb_offset);
> +
> +            if (read != qemu_ram_pagesize(rb)) {
> +                error_setg(errp, "Could not read page %zu from RAM Block %s",
> +                           page, rb->idstr);
> +                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;
> +            }
> +

Nit: unnecessary newline.

> +        } else {
> +            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;
> +            }
> +        }
> +    }
> +    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.
> @@ -1279,6 +1341,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();
> @@ -1320,11 +1383,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);
>          }
> @@ -1387,18 +1452,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 */
> +                WITH_QEMU_LOCK_GUARD(&mis->page_request_mutex) {
> +                    if (!ramblock_recv_bitmap_test(
> +                            rb, (void *)msg.arg.pagefault.address)) {
> +                        mark_postcopy_blocktime_begin(
> +                            msg.arg.pagefault.address,
> +                            msg.arg.pagefault.feat.ptid, rb);
> +                    }
> +                }

This part needs some more thought.

First of all, if the page is set already in receivedmap, it means it's
already installed, we shouldn't bother postcopy_mapped_ram_load_page().

But the problem is not only about that.

So far looks like the blocktime feature relies on the receivedmap, then it
means the easy way for this series is to support receivedmap too.  Then
with receivedmap, we should unify the receivedmap detection rather than
testing the bit at multiple places.

Say, you can provide a small helper like this:

diff --git a/migration/migration.c b/migration/migration.c
index 278cad502a..79f737bc69 100644
--- a/migration/migration.c
+++ b/migration/migration.c
@@ -573,12 +573,10 @@ int migrate_send_rp_message_req_pages(MigrationIncomingState *mis,
     return migrate_send_rp_message(mis, msg_type, msglen, bufc);
 }

-int migrate_send_rp_req_pages(MigrationIncomingState *mis,
-                              RAMBlock *rb, ram_addr_t start, uint64_t haddr,
-                              uint32_t tid)
+bool postcopy_page_received(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;
+    bool received;

     WITH_QEMU_LOCK_GUARD(&mis->page_request_mutex) {
         received = ramblock_recv_bitmap_test_byte_offset(rb, start);
@@ -598,11 +596,20 @@ int migrate_send_rp_req_pages(MigrationIncomingState *mis,
         }
     }

+    return received;
+}
+
+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));
+
     /*
      * 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 (postcopy_page_received(mis, rb, start, aligned, tid)) {
         return 0;
     }

Then I think we can use postcopy_page_received() here to avoid duplicating
the code.

It means with your feature we will also maintain the tree of page_requested
but I think it's fine; if we really want we can make that optional based on
whether mapped-ram enabled, but not yet required.

PS: I feel like there was a bug in existing code passing "haddr" (rather
than "aligned") into mark_postcopy_blocktime_begin() in the current impl of
migrate_send_rp_req_pages().  Please help to check if it's an issue, if
true we can have a separate patch fixing that first.  In general, I think
we should use a page-aligned address in both the tree and blocktime.

> +                if (!postcopy_mapped_ram_load_page(
> +                        mis, rb, rb_offset, msg.arg.pagefault.address,
> +                        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;
> +                }
>              }
>          }
>  
> @@ -1470,8 +1554,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.54.0
> 

-- 
Peter Xu



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

* Re: [RFC PATCH v2 7/7] migration: add eager load thread and setup for fast snapshot load
  2026-06-30  8:19 ` [RFC PATCH v2 7/7] migration: add eager load thread and setup for fast snapshot load Aadeshveer Singh
@ 2026-07-06 21:07   ` Peter Xu
  0 siblings, 0 replies; 16+ messages in thread
From: Peter Xu @ 2026-07-06 21:07 UTC (permalink / raw)
  To: Aadeshveer Singh; +Cc: qemu-devel, farosas, pbonzini, philmd, lvivier, ayoub

On Tue, Jun 30, 2026 at 01:49:40PM +0530, 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>
> ---
>  migration/migration.c    | 33 ++++++++++++++++--
>  migration/migration.h    |  5 +++
>  migration/postcopy-ram.c | 74 ++++++++++++++++++++++++++++++++++++++--
>  migration/postcopy-ram.h |  2 ++
>  migration/savevm.c       | 26 ++++++++++++++
>  migration/savevm.h       |  2 ++
>  migration/trace-events   |  2 ++
>  7 files changed, 140 insertions(+), 4 deletions(-)
> 
> diff --git a/migration/migration.c b/migration/migration.c
> index 074d3f2c69..9295794713 100644
> --- a/migration/migration.c
> +++ b/migration/migration.c
> @@ -730,6 +730,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)
>  {
> @@ -759,17 +764,41 @@ process_incoming_migration_co(void *opaque)
>      migrate_set_state(&mis->state, MIGRATION_STATUS_SETUP,
>                        MIGRATION_STATUS_ACTIVE);
>  

Below is correct, said that, it would be nice to add a comment explaining
why setup postcopy needs to be done here, especially, why it needs to be
before qemu_loadvm_state(); it's very not obvious but critical..

Something like this?

       /*
        * 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()) {
> +        if (qemu_loadvm_run_fast_snapshot_load(mis->from_src_file, mis,
> +                                               &local_err)) {
> +            goto fail;
> +        }
> +    }
>  
>      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 be9edac572..c31abd4339 100644
> --- a/migration/postcopy-ram.c
> +++ b/migration/postcopy-ram.c
> @@ -2159,7 +2159,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();
> @@ -2267,7 +2267,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);

Nitpick: this rename change can be a tiny separate patch.

>  
>      object_unref(OBJECT(migr));
>  
> @@ -2311,9 +2311,79 @@ 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;

Nit: return a negative (e.g. -1) might be clearer for an error.

> +        }
> +    }
> +    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;
> +
> +    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)) {
> +        migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
> +                          MIGRATION_STATUS_FAILED);
> +        goto out;
> +    }
> +    migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
> +                      MIGRATION_STATUS_COMPLETED);
> +
> +out:
> +    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
> + */
> +int 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;
> +    return 0;
> +}
> diff --git a/migration/postcopy-ram.h b/migration/postcopy-ram.h
> index 98d918713f..6d1a296795 100644
> --- a/migration/postcopy-ram.h
> +++ b/migration/postcopy-ram.h
> @@ -202,4 +202,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);
>  
> +int postcopy_ram_eager_load_setup(MigrationIncomingState *mis);
> +
>  #endif
> diff --git a/migration/savevm.c b/migration/savevm.c
> index 23adaf9dd9..5857fb8ac2 100644
> --- a/migration/savevm.c
> +++ b/migration/savevm.c
> @@ -2959,6 +2959,32 @@ static bool postcopy_pause_incoming(MigrationIncomingState *mis)
>      return true;
>  }
>  
> +/*
> + * Starts the VM and launches the eager thread for fast snapshot load
> + */
> +int qemu_loadvm_run_fast_snapshot_load(QEMUFile *f, MigrationIncomingState *mis,
> +                                       Error **errp)
> +{
> +    ERRP_GUARD();
> +    int ret = 0;
> +
> +    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);
> +
> +    ret = postcopy_ram_eager_load_setup(mis);

This function never fails, can make it return void and remove the err
handling.

> +    if (ret) {
> +        error_prepend(errp,
> +                      "Failed to setup eager load for fast snapshot load: ");
> +        return ret;
> +    }
> +
> +    return ret;
> +}
> +
>  int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
>                             Error **errp)
>  {
> diff --git a/migration/savevm.h b/migration/savevm.h
> index 96fdf96d4e..f17d7057e1 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);
> +int qemu_loadvm_run_fast_snapshot_load(QEMUFile *f, MigrationIncomingState *mis,
> +                                       Error **errp);
>  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.54.0
> 

-- 
Peter Xu



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

end of thread, other threads:[~2026-07-06 21:08 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-30  8:19 [RFC PATCH v2 0/7] migration: fast snapshot load Aadeshveer Singh
2026-06-30  8:19 ` [RFC PATCH v2 1/7] migration/tests: remove capability conflict test postcopy-ram+mapped-ram Aadeshveer Singh
2026-07-06 19:02   ` Peter Xu
2026-06-30  8:19 ` [RFC PATCH v2 2/7] migration: Propagate error in postcopy setup functions Aadeshveer Singh
2026-07-06 19:13   ` Peter Xu
2026-06-30  8:19 ` [RFC PATCH v2 3/7] migration: Use file_bmap for RAMBlock during incoming file load Aadeshveer Singh
2026-07-06 19:29   ` Peter Xu
2026-06-30  8:19 ` [RFC PATCH v2 4/7] migration: Make qemu_get_buffer_at() thread-safe Aadeshveer Singh
2026-07-06 19:34   ` Peter Xu
2026-06-30  8:19 ` [RFC PATCH v2 5/7] migration: add RAMBlock field and helper for fast snapshot load Aadeshveer Singh
2026-07-06 20:03   ` Peter Xu
2026-06-30  8:19 ` [RFC PATCH v2 6/7] migration: add support for fault thread to load pages from disk Aadeshveer Singh
2026-07-06 20:35   ` Peter Xu
2026-06-30  8:19 ` [RFC PATCH v2 7/7] migration: add eager load thread and setup for fast snapshot load Aadeshveer Singh
2026-07-06 21:07   ` Peter Xu
2026-07-06 19:16 ` [RFC PATCH v2 0/7] migration: " 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.