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 <[email protected]>
> ---
>  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


Reply via email to