On Fri, Sep 11, 2026 at 04:23:44PM +0200, Denis V. Lunev wrote:
> From: Denis V. Lunev <[email protected]>
> 
> A post_load hook which only rearranges the memory topology forces a
> flatview rebuild as its section is read, and the cost of a rebuild grows
> with the number of regions in the machine. A device which does this once
> per vCPU therefore scales badly on the destination.
> 
> Add post_load_deferrable. A vmsd which sets it has its hook queued during
> the load and run once the stream has been consumed, in the order the
> hooks would have fired, with the whole drain sharing one memory
> transaction.
> 
> Opt-in, because deferral is not free in general. A hook which can fail
> must not be deferred: failing after the stream is consumed means the
> source has already been told the migration succeeded, and may release a
> guest the destination never started. A hook which reads guest memory must
> not be deferred either, since the drain runs with the topology in flux.
> Postcopy is excluded because its listen thread walks the same stream
> concurrently.
> 
> CC: Peter Xu <[email protected]>
> CC: Fabiano Rosas <[email protected]>
> CC: Paolo Bonzini <[email protected]>
> CC: Zhao Liu <[email protected]>
> Signed-off-by: Denis V. Lunev <[email protected]>
> ---
>  include/migration/vmstate.h |  40 +++++++++++++
>  migration/savevm.c          |  23 ++++++++
>  migration/vmstate.c         |  67 +++++++++++++++++++++-
>  tests/unit/test-vmstate.c   | 111 ++++++++++++++++++++++++++++++++++++
>  4 files changed, 239 insertions(+), 2 deletions(-)
> 
> diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
> index e72c3fae9a..16045319d5 100644
> --- a/include/migration/vmstate.h
> +++ b/include/migration/vmstate.h
> @@ -303,6 +303,29 @@ struct VMStateDescription {
>      bool (*pre_load_errp)(void *opaque, Error **errp);
>      int (*post_load)(void *opaque, int version_id);
>      bool (*post_load_errp)(void *opaque, int version_id, Error **errp);
> +
> +    /*
> +     * Run .post_load() once the whole stream has been loaded rather than
> +     * as this section is read, so that it sees a machine whose devices
> +     * have all been restored, and so that several of them can share the
> +     * work they would each repeat.
> +     *
> +     * Three things must hold of a hook before it may be deferred.
> +     *
> +     * It must not fail. By the time the queue is drained the stream has
> +     * been consumed, so a source may already have been told the migration
> +     * succeeded and may have released the guest; there is nothing left to
> +     * report a failure to. A hook which fails here is a bug in its vmsd
> +     * and is fatal.
> +     *
> +     * Nothing else in the load may depend on what it does. The hooks run
> +     * after every section has been read, so anything a later section needs
> +     * to observe must not be produced here.
> +     *
> +     * It must not read guest memory or resolve an address space, because
> +     * the drain runs as one batch with the memory topology in flux.
> +     */
> +    bool post_load_deferrable;
>      int (*pre_save)(void *opaque);
>      bool (*pre_save_errp)(void *opaque, Error **errp);
>  
> @@ -1299,6 +1322,23 @@ bool vmstate_save_vmsd(QEMUFile *f, const 
> VMStateDescription *vmsd,
>  
>  bool vmstate_section_needed(const VMStateDescription *vmsd, void *opaque);
>  
> +/**
> + * vmstate_post_load_defer_begin: Queue deferrable post_load hooks
> + *
> + * Between this and vmstate_post_load_defer_finish(), a post_load hook whose
> + * vmsd sets post_load_deferrable is recorded rather than called. Hooks are
> + * queued in the order they would have run.
> + */
> +void vmstate_post_load_defer_begin(void);
> +
> +/**
> + * vmstate_post_load_defer_finish: Stop deferring and drain the queue
> + * @run: run the queued hooks, in order; when false, discard them
> + *
> + * Returns false if a hook failed, in which case the rest are discarded.
> + */
> +bool vmstate_post_load_defer_finish(bool run, Error **errp);
> +
>  #define  VMSTATE_INSTANCE_ID_ANY  -1
>  
>  /* Returns: 0 on success, -1 on failure */
> diff --git a/migration/savevm.c b/migration/savevm.c
> index 4b590ea672..2352dcf684 100644
> --- a/migration/savevm.c
> +++ b/migration/savevm.c
> @@ -3128,6 +3128,7 @@ int qemu_loadvm_state(QEMUFile *f, Error **errp)
>  {
>      MigrationState *s = migrate_get_current();
>      MigrationIncomingState *mis = migration_incoming_get_current();
> +    bool defer_post_load;
>      int ret;
>  
>      if (qemu_savevm_state_blocked(errp)) {
> @@ -3147,7 +3148,29 @@ int qemu_loadvm_state(QEMUFile *f, Error **errp)
>  
>      cpu_synchronize_all_pre_loadvm();
>  
> +    /*
> +     * The postcopy listen thread walks the same stream concurrently, so the
> +     * queue would need locking and a defined owner for the drain.
> +     */
> +    defer_post_load = !migrate_postcopy_ram();

Just to mention we have two other paths that will enable this too:

qmp_xen_load_devices_state
load_snapshot

It looks all fine at least considering the hyper-v scope, but still raise
this in case it's not expected.

The other thing is, IMHO we should enable this feature for both precopy and
postcopy.  Postcopy loads that in the package, please kindly share more on
the above comment on disabling it; I didn't directly get that part.

> +    if (defer_post_load) {
> +        vmstate_post_load_defer_begin();
> +    }
> +
>      ret = qemu_loadvm_state_main(f, mis, errp);
> +
> +    if (defer_post_load) {
> +        /*
> +         * A deferrable hook only rearranges the memory topology, so the
> +         * whole drain can share one flatview rebuild.
> +         */
> +        memory_region_transaction_begin();

IMHO we should document this behavior in the "deferrable" interface above,
this is important knowledge to know that the whole deferred vmsd loads are
wrapped with boosted transaction depths and all memory access is illegal.

AFAIU, that partly supplement my other reply, that the whole thing is not
about "defer" or not, but about "batching MR update".  It can still be at
the end of course, but it's not the major goal, the major goal is about
memory updates.

Thanks,

> +        if (!vmstate_post_load_defer_finish(ret == 0, errp)) {
> +            ret = -EINVAL;
> +        }
> +        memory_region_transaction_commit();
> +    }

-- 
Peter Xu


Reply via email to