From: Peter Xu <peterx@redhat.com>
To: Juraj Marcin <jmarcin@redhat.com>
Cc: qemu-devel@nongnu.org,
"Dr. David Alan Gilbert" <dave@treblig.org>,
Jiri Denemark <jdenemar@redhat.com>,
Fabiano Rosas <farosas@suse.de>
Subject: Re: [PATCH v2 2/4] migration: Move postcopy_ram_listen_thread() to postcopy-ram.c
Date: Wed, 29 Oct 2025 15:53:43 -0400 [thread overview]
Message-ID: <aQJwx5IBnwk7SxkJ@x1.local> (raw)
In-Reply-To: <20251027154115.4138677-3-jmarcin@redhat.com>
On Mon, Oct 27, 2025 at 04:41:09PM +0100, Juraj Marcin wrote:
> From: Juraj Marcin <jmarcin@redhat.com>
>
> This patch addresses a TODO about moving postcopy_ram_listen_thread() to
> postcopy file. Furthermore, this patch adds a pair of functions,
> postcopy_incoming_setup() and postcopy_incoming_cleanup(), which sets up
> and cleans the postcopy_ram_incoming state and the listen thread.
It would be great to separate code movements and changes.
Meanwhile, this patch won't apply cleanly on top of the staging branch that
I kept.. it'll be great if you could rebase this series to the branch when
repost:
https://gitlab.com/peterx/qemu/-/commits/staging
>
> Signed-off-by: Juraj Marcin <jmarcin@redhat.com>
> ---
> migration/migration.c | 2 +-
> migration/postcopy-ram.c | 144 +++++++++++++++++++++++++++++++++++++++
> migration/postcopy-ram.h | 3 +
> migration/savevm.c | 131 +----------------------------------
> 4 files changed, 150 insertions(+), 130 deletions(-)
>
> diff --git a/migration/migration.c b/migration/migration.c
> index e9acd0f63b..8827884a15 100644
> --- a/migration/migration.c
> +++ b/migration/migration.c
> @@ -892,7 +892,7 @@ process_incoming_migration_co(void *opaque)
> * but managed to complete within the precopy period, we can use
> * the normal exit.
> */
> - postcopy_ram_incoming_cleanup(mis);
> + postcopy_incoming_cleanup(mis);
> } else if (ret >= 0) {
> /*
> * Postcopy was started, cleanup should happen at the end of the
> diff --git a/migration/postcopy-ram.c b/migration/postcopy-ram.c
> index 5471efb4f0..dbbb2dfb78 100644
> --- a/migration/postcopy-ram.c
> +++ b/migration/postcopy-ram.c
> @@ -2077,3 +2077,147 @@ bool postcopy_is_paused(MigrationStatus status)
> return status == MIGRATION_STATUS_POSTCOPY_PAUSED ||
> status == MIGRATION_STATUS_POSTCOPY_RECOVER_SETUP;
> }
> +
> +/*
> + * Triggered by a postcopy_listen command; this thread takes over reading
> + * the input stream, leaving the main thread free to carry on loading the rest
> + * of the device state (from RAM).
> + */
> +static void *postcopy_listen_thread(void *opaque)
> +{
> + MigrationIncomingState *mis = migration_incoming_get_current();
> + QEMUFile *f = mis->from_src_file;
> + int load_res;
> + MigrationState *migr = migrate_get_current();
> + Error *local_err = NULL;
> +
> + object_ref(OBJECT(migr));
> +
> + migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE,
> + MIGRATION_STATUS_POSTCOPY_ACTIVE);
> + qemu_event_set(&mis->thread_sync_event);
> + trace_postcopy_ram_listen_thread_start();
> +
> + rcu_register_thread();
> + /*
> + * Because we're a thread and not a coroutine we can't yield
> + * in qemu_file, and thus we must be blocking now.
> + */
> + qemu_file_set_blocking(f, true, &error_fatal);
> +
> + /* TODO: sanity check that only postcopiable data will be loaded here */
> + load_res = qemu_loadvm_state_main(f, mis, &local_err);
> +
> + /*
> + * This is tricky, but, mis->from_src_file can change after it
> + * returns, when postcopy recovery happened. In the future, we may
> + * want a wrapper for the QEMUFile handle.
> + */
> + f = mis->from_src_file;
> +
> + /* And non-blocking again so we don't block in any cleanup */
> + qemu_file_set_blocking(f, false, &error_fatal);
> +
> + trace_postcopy_ram_listen_thread_exit();
> + if (load_res < 0) {
> + qemu_file_set_error(f, load_res);
> + dirty_bitmap_mig_cancel_incoming();
> + if (postcopy_state_get() == POSTCOPY_INCOMING_RUNNING &&
> + !migrate_postcopy_ram() && migrate_dirty_bitmaps())
> + {
> + error_report("%s: loadvm failed during postcopy: %d. All states "
> + "are migrated except dirty bitmaps. Some dirty "
> + "bitmaps may be lost, and present migrated dirty "
> + "bitmaps are correctly migrated and valid.",
> + __func__, load_res);
> + load_res = 0; /* prevent further exit() */
> + } else {
> + error_prepend(&local_err,
> + "loadvm failed during postcopy: %d: ", load_res);
> + migrate_set_error(migr, local_err);
> + error_report_err(local_err);
> + migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
> + MIGRATION_STATUS_FAILED);
> + }
> + }
> + if (load_res >= 0) {
> + /*
> + * This looks good, but it's possible that the device loading in the
> + * main thread hasn't finished yet, and so we might not be in 'RUN'
> + * state yet; wait for the end of the main thread.
> + */
> + qemu_event_wait(&mis->main_thread_load_event);
> + }
> + postcopy_incoming_cleanup(mis);
Here I did notice that this replaced the old
postcopy_ram_incoming_cleanup(). I'm just curious: is it needed to check
postcopy-ram=on once more?
The two callers of postcopy_incoming_cleanup() should always have
postcopy-ram enabled, right?
> +
> + if (load_res < 0) {
> + /*
> + * If something went wrong then we have a bad state so exit;
> + * depending how far we got it might be possible at this point
> + * to leave the guest running and fire MCEs for pages that never
> + * arrived as a desperate recovery step.
> + */
> + rcu_unregister_thread();
> + exit(EXIT_FAILURE);
> + }
> +
> + migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
> + MIGRATION_STATUS_COMPLETED);
> + /*
> + * If everything has worked fine, then the main thread has waited
> + * for us to start, and we're the last use of the mis.
> + * (If something broke then qemu will have to exit anyway since it's
> + * got a bad migration state).
> + */
> + bql_lock();
> + migration_incoming_state_destroy();
> + bql_unlock();
> +
> + rcu_unregister_thread();
> + mis->have_listen_thread = false;
> + postcopy_state_set(POSTCOPY_INCOMING_END);
> +
> + object_unref(OBJECT(migr));
> +
> + return NULL;
> +}
> +
> +int postcopy_incoming_setup(MigrationIncomingState *mis, Error **errp)
> +{
> + /*
> + * Sensitise RAM - can now generate requests for blocks that don't exist
> + * However, at this point the CPU shouldn't be running, and the IO
> + * shouldn't be doing anything yet so don't actually expect requests
> + */
> + if (migrate_postcopy_ram()) {
> + if (postcopy_ram_incoming_setup(mis)) {
> + postcopy_ram_incoming_cleanup(mis);
> + error_setg(errp, "Failed to setup incoming postcopy RAM blocks");
> + return -1;
> + }
> + }
> +
> + trace_loadvm_postcopy_handle_listen("after uffd");
> +
> + if (postcopy_notify(POSTCOPY_NOTIFY_INBOUND_LISTEN, errp)) {
> + return -1;
> + }
> +
> + mis->have_listen_thread = true;
> + postcopy_thread_create(mis, &mis->listen_thread,
> + MIGRATION_THREAD_DST_LISTEN,
> + postcopy_listen_thread, QEMU_THREAD_DETACHED);
> +
> + return 0;
> +}
> +
> +int postcopy_incoming_cleanup(MigrationIncomingState *mis)
> +{
> + int rc = 0;
> +
> + if (migrate_postcopy_ram()) {
> + rc = postcopy_ram_incoming_cleanup(mis);
> + }
> +
> + return rc;
> +}
> diff --git a/migration/postcopy-ram.h b/migration/postcopy-ram.h
> index ca19433b24..a080dd65a7 100644
> --- a/migration/postcopy-ram.h
> +++ b/migration/postcopy-ram.h
> @@ -199,4 +199,7 @@ bool postcopy_is_paused(MigrationStatus status);
> void mark_postcopy_blocktime_begin(uintptr_t addr, uint32_t ptid,
> RAMBlock *rb);
>
> +int postcopy_incoming_setup(MigrationIncomingState *mis, Error **errp);
> +int postcopy_incoming_cleanup(MigrationIncomingState *mis);
> +
> #endif
> diff --git a/migration/savevm.c b/migration/savevm.c
> index 7b35ec4dd0..96a2699ca7 100644
> --- a/migration/savevm.c
> +++ b/migration/savevm.c
> @@ -2087,112 +2087,6 @@ static int loadvm_postcopy_ram_handle_discard(MigrationIncomingState *mis,
> return 0;
> }
>
> -/*
> - * Triggered by a postcopy_listen command; this thread takes over reading
> - * the input stream, leaving the main thread free to carry on loading the rest
> - * of the device state (from RAM).
> - * (TODO:This could do with being in a postcopy file - but there again it's
> - * just another input loop, not that postcopy specific)
> - */
> -static void *postcopy_ram_listen_thread(void *opaque)
> -{
> - MigrationIncomingState *mis = migration_incoming_get_current();
> - QEMUFile *f = mis->from_src_file;
> - int load_res;
> - MigrationState *migr = migrate_get_current();
> - Error *local_err = NULL;
> -
> - object_ref(OBJECT(migr));
> -
> - migrate_set_state(&mis->state, MIGRATION_STATUS_ACTIVE,
> - MIGRATION_STATUS_POSTCOPY_ACTIVE);
> - qemu_event_set(&mis->thread_sync_event);
> - trace_postcopy_ram_listen_thread_start();
> -
> - rcu_register_thread();
> - /*
> - * Because we're a thread and not a coroutine we can't yield
> - * in qemu_file, and thus we must be blocking now.
> - */
> - qemu_file_set_blocking(f, true, &error_fatal);
> -
> - /* TODO: sanity check that only postcopiable data will be loaded here */
> - load_res = qemu_loadvm_state_main(f, mis, &local_err);
> -
> - /*
> - * This is tricky, but, mis->from_src_file can change after it
> - * returns, when postcopy recovery happened. In the future, we may
> - * want a wrapper for the QEMUFile handle.
> - */
> - f = mis->from_src_file;
> -
> - /* And non-blocking again so we don't block in any cleanup */
> - qemu_file_set_blocking(f, false, &error_fatal);
> -
> - trace_postcopy_ram_listen_thread_exit();
> - if (load_res < 0) {
> - qemu_file_set_error(f, load_res);
> - dirty_bitmap_mig_cancel_incoming();
> - if (postcopy_state_get() == POSTCOPY_INCOMING_RUNNING &&
> - !migrate_postcopy_ram() && migrate_dirty_bitmaps())
> - {
> - error_report("%s: loadvm failed during postcopy: %d. All states "
> - "are migrated except dirty bitmaps. Some dirty "
> - "bitmaps may be lost, and present migrated dirty "
> - "bitmaps are correctly migrated and valid.",
> - __func__, load_res);
> - load_res = 0; /* prevent further exit() */
> - } else {
> - error_prepend(&local_err,
> - "loadvm failed during postcopy: %d: ", load_res);
> - migrate_set_error(migr, local_err);
> - error_report_err(local_err);
> - migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
> - MIGRATION_STATUS_FAILED);
> - }
> - }
> - if (load_res >= 0) {
> - /*
> - * This looks good, but it's possible that the device loading in the
> - * main thread hasn't finished yet, and so we might not be in 'RUN'
> - * state yet; wait for the end of the main thread.
> - */
> - qemu_event_wait(&mis->main_thread_load_event);
> - }
> - postcopy_ram_incoming_cleanup(mis);
> -
> - if (load_res < 0) {
> - /*
> - * If something went wrong then we have a bad state so exit;
> - * depending how far we got it might be possible at this point
> - * to leave the guest running and fire MCEs for pages that never
> - * arrived as a desperate recovery step.
> - */
> - rcu_unregister_thread();
> - exit(EXIT_FAILURE);
> - }
> -
> - migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
> - MIGRATION_STATUS_COMPLETED);
> - /*
> - * If everything has worked fine, then the main thread has waited
> - * for us to start, and we're the last use of the mis.
> - * (If something broke then qemu will have to exit anyway since it's
> - * got a bad migration state).
> - */
> - bql_lock();
> - migration_incoming_state_destroy();
> - bql_unlock();
> -
> - rcu_unregister_thread();
> - mis->have_listen_thread = false;
> - postcopy_state_set(POSTCOPY_INCOMING_END);
> -
> - object_unref(OBJECT(migr));
> -
> - return NULL;
> -}
> -
> /* After this message we must be able to immediately receive postcopy data */
> static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis,
> Error **errp)
> @@ -2218,32 +2112,11 @@ static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis,
>
> trace_loadvm_postcopy_handle_listen("after discard");
>
> - /*
> - * Sensitise RAM - can now generate requests for blocks that don't exist
> - * However, at this point the CPU shouldn't be running, and the IO
> - * shouldn't be doing anything yet so don't actually expect requests
> - */
> - if (migrate_postcopy_ram()) {
> - if (postcopy_ram_incoming_setup(mis)) {
> - postcopy_ram_incoming_cleanup(mis);
> - error_setg(errp, "Failed to setup incoming postcopy RAM blocks");
> - return -1;
> - }
> - }
> + int rc = postcopy_incoming_setup(mis, errp);
>
> - trace_loadvm_postcopy_handle_listen("after uffd");
> -
> - if (postcopy_notify(POSTCOPY_NOTIFY_INBOUND_LISTEN, errp)) {
> - return -1;
> - }
> -
> - mis->have_listen_thread = true;
> - postcopy_thread_create(mis, &mis->listen_thread,
> - MIGRATION_THREAD_DST_LISTEN,
> - postcopy_ram_listen_thread, QEMU_THREAD_DETACHED);
> trace_loadvm_postcopy_handle_listen("return");
>
> - return 0;
> + return rc;
> }
>
> static void loadvm_postcopy_handle_run_bh(void *opaque)
> --
> 2.51.0
>
--
Peter Xu
next prev parent reply other threads:[~2025-10-29 19:56 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-10-27 15:41 [PATCH v2 0/4] migration: Introduce POSTCOPY_DEVICE state Juraj Marcin
2025-10-27 15:41 ` [PATCH v2 1/4] migration: Do not try to start VM if disk activation fails Juraj Marcin
2025-10-27 15:41 ` [PATCH v2 2/4] migration: Move postcopy_ram_listen_thread() to postcopy-ram.c Juraj Marcin
2025-10-29 19:53 ` Peter Xu [this message]
2025-10-30 13:08 ` Juraj Marcin
2025-10-30 15:16 ` Peter Xu
2025-10-27 15:41 ` [PATCH v2 3/4] migration: Refactor all incoming cleanup into migration_incoming_cleanup() Juraj Marcin
2025-10-29 22:02 ` Peter Xu
2025-10-27 15:41 ` [PATCH v2 4/4] migration: Introduce POSTCOPY_DEVICE state Juraj Marcin
2025-10-29 22:57 ` Peter Xu
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aQJwx5IBnwk7SxkJ@x1.local \
--to=peterx@redhat.com \
--cc=dave@treblig.org \
--cc=farosas@suse.de \
--cc=jdenemar@redhat.com \
--cc=jmarcin@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.