On 3/20/26 18:34, Denis V. Lunev wrote:
> When libvirtd reconnects to a running QEMU process that had an
> in-progress migration, qemuProcessReconnect first connects the
> monitor and only later recovers the migration job. During this window
> the async job is VIR_ASYNC_JOB_NONE, so any MIGRATION status events
> from QEMU are silently dropped by qemuProcessHandleMigrationStatus.
>
> If the migration was already cancelled or completed by QEMU during
> this window, no further events will be emitted. When
> qemuMigrationSrcCancelUnattended later restores the async job and
> calls qemuMigrationSrcCancel with wait=true, the wait loop calls
> qemuDomainObjWait (virCondWait with no timeout) and blocks forever
> waiting for an event that will never arrive.
>
> Fix this by re-querying QEMU migration state with
> qemuMigrationAnyRefreshStatus after restoring the async job but before
> calling qemuMigrationSrcCancel. If QEMU has already reached a terminal
> state, the cancel is skipped.
>
> Signed-off-by: Denis V. Lunev <[email protected]>
> CC: Jiri Denemark <[email protected]>
> CC: Peter Krempa <[email protected]>
> CC: Michal Privoznik <[email protected]>
> CC: Efim Shevrin <[email protected]>
> ---
> v1 -> v2: Instead of querying QEMU with query-migrate inside
> qemuMigrationSrcCancel, use qemuMigrationAnyRefreshStatus in
> qemuMigrationSrcCancelUnattended after restoring the async job
> to re-check migration state before the actual cancel.
>
>  src/qemu/qemu_migration.c | 18 ++++++++++++++----
>  1 file changed, 14 insertions(+), 4 deletions(-)
>
> diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
> index fec808ccfb..a4bd7efa09 100644
> --- a/src/qemu/qemu_migration.c
> +++ b/src/qemu/qemu_migration.c
> @@ -7330,6 +7330,7 @@ int
>  qemuMigrationSrcCancelUnattended(virDomainObj *vm,
>                                   virDomainJobObj *oldJob)
>  {
> +    virDomainJobStatus migStatus = VIR_DOMAIN_JOB_STATUS_NONE;
>      bool storage = false;
>      size_t i;
>  
> @@ -7348,11 +7349,20 @@ qemuMigrationSrcCancelUnattended(virDomainObj *vm,
>                                       VIR_JOB_NONE);
>      }
>  
> -    /* We're inside a MODIFY job and the restored MIGRATION_OUT async job is
> -     * used only for processing migration events from QEMU. Thus we don't 
> want
> -     * to start a nested job for talking to QEMU.
> +    /* Query the actual migration state from QEMU. The state passed to
> +     * qemuProcessRecoverMigrationOut may be stale: QEMU could have
> +     * reached a terminal state between that initial query and the async
> +     * job restore above, with the corresponding event silently dropped.
>       */
> -    qemuMigrationSrcCancel(vm, VIR_ASYNC_JOB_NONE, true);
> +    qemuMigrationAnyRefreshStatus(vm, VIR_ASYNC_JOB_NONE, &migStatus);
> +
> +    if (migStatus != VIR_DOMAIN_JOB_STATUS_CANCELED) {
> +        /* We're inside a MODIFY job and the restored MIGRATION_OUT async
> +         * job is used only for processing migration events from QEMU.
> +         * Thus we don't want to start a nested job for talking to QEMU.
> +         */
> +        qemuMigrationSrcCancel(vm, VIR_ASYNC_JOB_NONE, true);
> +    }
>  
>      virDomainObjEndAsyncJob(vm);
>  
ping v4.

NOTE: I have seen something very close to this (I believe that this is
this) in production downstream. 

Reply via email to