Skip to content

Backport Asynchronization Series from Upstream to Align Patchset - #207

Open
phip1611 wants to merge 79 commits into
cyberus-technology:gardenlinuxfrom
phip1611:gardenlinux-backport-asynchronization
Open

phip1611 wants to merge 79 commits into
cyberus-technology:gardenlinuxfrom
phip1611:gardenlinux-backport-asynchronization

Conversation

@phip1611

@phip1611 phip1611 commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

This first reverts everything building on our fork's asynchronization commit series, then applies the incompatible (and better) upstream solution and then reapplys our commits with minimal changes to make them compatible with the new base.

This is an effort to prepare the v54 bump and to test that upstream code still passes our test suite. This backport should be by far the most invasive backport.

Closes cloud-hypervisor#697.

Patchset was created with AI assistance.

libvirt pipeline: https://gitlab.cyberus-technology.de/cyberus/cloud/libvirt/-/merge_requests/294

Hints for Reviewers

Details

Branch gardenlinux-backport-asynchronization, 83 commits on top of
68e6d4ed7 ("hypervisor: Set fw_cfg as default feature").

Structure of the series

The series has four blocks. Reading it in order is the intended way to review
it; the net diff against 68e6d4ed7 is confined to 13 files and is a better
way to judge the end state.

  1. Commits 1-40: reverts, newest fork commit first. Everything that builds
    on the fork's migration worker is reverted before the worker itself, so each
    step stays compilable. The naive order (async core first) dead-ends at
    034935aed; ~27 dependent commits have to go first.
  2. Commits 41-47: upstream cherry-picks - 15cab7ee6, e03456769,
    5d835bdff, 796fc055b, 7c7a827de, a56594324, 6a16b65ea.
  3. Commits 48-82: the fork's features re-applied on upstream's worker, in
    their original chronological order, plus a few align/port commits.
  4. Commit 83: a comment-only correction in vm-migration/src/protocol.rs.

Commit message conventions

  • Cherry-picks carry, between subject and body:
    Cherry-picked from upstream commit <sha> ("<subject>").
    A following paragraph names the fork-specific adaptation, if any was needed.
  • Re-applied fork commits are titled <component>: Reapply "<original subject>" and their body starts with This reverts commit <revert-sha>.,
    i.e. they point at their own revert in block 1. An "Adapted to upstream's
    migration worker: ..." paragraph explains every deviation.
    (git's own default is Reapply "<subject>", but scripts/gitlint/rules
    requires a component prefix, hence the prefixed form.)
  • Four titles were shortened to fit 72 columns; those bodies name the original
    subject verbatim.
  • Re-applied commits keep their original author and that author's
    On-behalf-of/Signed-off-by trailers, with a second Signed-off-by added.

Deliberately not re-applied

  • a904e9b9d, 034935aed, e3977923b, f7303f34a, 3b3eda17b,
    2a2f4e618, c70dd7790 - the fork's async core; upstream's series
    implements the same thing.
  • 27ba41b55 ("limit to running VMs only") - upstream's worker carries the
    initial VM state and resumes after a failure only if the VM was running, so
    the fork-only guard is redundant. Migrating a paused source VM works
    again
    ; the revert therefore also adds the missing "don't pause twice"
    guard to do_memory_migration(), as upstream has it.
  • 838ea2e71 ("streamline migration failure cleanup") - upstream's worker
    already has the resulting shape.
  • cf1874233 ("switch to non-blocking SendMigration call") - upstream's
    worker already dispatches without blocking. Only the fork-only keep_alive
    option survives, as the separate commit "vmm: keep the VMM alive after a
    migration if requested"
    .

Where the fork features hook into upstream's worker now

  • VmOwnership::Migration gained device_manager: Weak<Mutex<DeviceManager>>
    next to upstream's vm_info_response, so the epoll thread keeps draining
    virtio activations. This fills upstream's // TODO: ... virtio activation handling while migrating.
  • The postponed reboot/shutdown state is passed through
    MigrationWorker::spawn(); the Exit/Reset/GuestExit arms of the
    control loop fill upstream's two lifecycle // TODOs.
  • Cancellation: the Arc<AtomicBool> lives on MigrationWorkerHandle and is
    reached through VmOwnership::Migration, so the fork's separate
    Vmm::migration_thread_handle field is gone (it only existed because the
    flag could not be reached through the old enum). vm_cancel_migration() is
    panic-free now.
  • MigrationWorkerResult carries the migration config back to the VMM thread,
    which is how check_migration() evaluates keep_alive.
  • The progress snapshot stays the global MIGRATION_PROGRESS_SNAPSHOT. It is
    installed after the last fallible step before the hand-over and is marked
    failed when spawning the worker fails, so it can no longer stay Ongoing
    forever.

Behaviour changes worth a close look

Everything below is either upstream's behaviour or follows from the decisions
above - none of it is accidental, but all of it is externally visible:

  • Paused source VMs can be migrated again (see 27ba41b55 above).
  • After a successful migration the source VM is shut down explicitly
    (vm.shutdown()), which is upstream's code. It resumes device workers before
    joining them, at a point where the disk locks are already released and the
    destination has taken over, and the control loop now blocks on those joins.
    Upside: with keep_alive=on the fork used to leak the detached threads.
  • vm.deleted event: now emitted after the shutdown instead of before, and
    also for a created-but-never-booted VM (upstream's vm_delete).
  • A cancelled migration now replays a postponed reboot/shutdown. The replay
    sits inside try_resume_vm_after_failed_migration(), which is what the fork
    did too (its try_resume_vm contained the replay); the .context() strings
    still say "after failed migration" for that reason.
  • vm_info during a migration returns the snapshot captured at dispatch
    time, so device_tree is populated and state is the pre-migration state,
    instead of the fork's hardcoded Running + device_tree: None.
  • vm_boot/vm_restore error variants follow upstream (VmAlreadyCreated,
    VmMissingConfig), and vm_add_generic_vhost_user is no longer the fork's
    unimplemented!() stub (its #[ignore]d unit test runs again).
  • Three seccomp rules (rt_sigprocmask, getcwd, clock_nanosleep) leave
    http_api_thread_rules(); they were added only for the blocking
    send-migration call. SYS_read stays.
  • ch-remote send-migration is unchanged: it still waits by default and
    polls vm.migration-progress, with --dispatch to opt out. Upstream's test
    commit 6a16b65ea assumes fire-and-forget, so the TCP timeout test passes
    --dispatch (separate commit).

Unchanged on purpose (safe to skip while reviewing)

  • vmm/src/migration_transport.rs -> vmm/src/migration/transport.rs is a
    pure rename, zero content change: keep-alive stream, TCP-thread
    cancellation, worker-failure fixes, thread cleanup are all untouched.
  • cloud-hypervisor/src/bin/ch-remote.rs, vmm/src/vcpu_throttling.rs, the
    TLS stack and all of vm-migration/ except the protocol comment are
    byte-identical to 68e6d4ed7.
  • No event was renamed, added or dropped (only vm.deleted moved, see above),
    and the wire protocol is unchanged - Command::KeepAlive = 8 /
    CompletePaused = 9 stay, so migration between fork builds keeps working.

Align/doc commits added on top of the pure revert/pick/reapply work

  • openapi, docs: document the migration-progress endpoint
  • vmm, openapi: document the keep_alive send-migration option
  • openapi, docs: document the cancel-migration endpoint
    (these three sit directly after the commit that introduces the respective
    surface; the endpoints were previously in neither docs/api.md nor the
    OpenAPI spec)
  • vm-migration: describe the discriminant collision completely - comment
    only; records that upstream also uses discriminant 9 (PageFault), which
    this branch does not need, and that fork <-> upstream migration is therefore
    knowingly impossible.

Verification performed

  • cargo check --package cloud-hypervisor on every commit of the series
    (the branch is bisectable; cargo check -p vmm alone is broken in this repo
    because vmm's default features are empty).
  • On the tip: the CI jobs Clippy (kvm), Clippy (mshv), Clippy (mshv+kvm),
    Clippy (tdx,kvm), Clippy (default features), Build (mshv),
    cargo clippy -p cloud-hypervisor --test integration (that file is
    cfg-gated, a plain check compiles it to nothing), cargo test -p vmm --features kvm --lib (88/88), cargo +nightly fuzz check with
    RUSTFLAGS=-D warnings, and the OpenAPI validator CI uses
    (openapitools/openapi-generator-cli validate).
  • gitlint with this repo's custom rules over all 83 commits.
  • Integration tests and the external libvirt-tests suite were not run.
    The two changes most likely to show up there are the explicit source-VM
    shutdown and the vm.deleted event order.

Known open points

  • VmSendMigrationData::keep_alive is the only field of that struct without
    #[serde(default)], so a JSON body omitting it is rejected, although the
    CLI syntax and the new OpenAPI entry treat it as optional. Pre-existing, not
    changed here.
  • The vm.migration-progress and vm.cancel-migration endpoints still have no
    D-Bus counterpart; only the HTTP surface is documented.
  • fuzz/Cargo.lock is stale at the base commit already (fw_cfg fallout). CI's
    fuzz job does not use --locked, so it does not fail.

Too be honest, seeing this, I am unsure if our approach is the right choice. My intuition tells me to recreate v54 directly commit by commit (or commit series by commit series) rather than investing time into the gardenlinux branch. But I am happy to stick to our plan. Just unsure if it still brings us anything. I had to revert the world to make this possible.

However, once we merged this, the other backports are far simpler.

This reverts commit 4fb29bc.

This change is independent of the migration worker, but its code
sits inside the reverted region. It is re-applied unchanged later
in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 705ed46.

This change is independent of the migration worker, but its code
sits inside the reverted region. It is re-applied unchanged later
in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 636637c.

This change is independent of the migration worker, but its code
sits inside the reverted region. It is re-applied unchanged later
in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit e38e586.

This change is independent of the migration worker, but its code
sits inside the reverted region. It is re-applied unchanged later
in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit d00bdc0.

This change is independent of the migration worker, but its code
sits inside the reverted region. It is re-applied unchanged later
in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit e6cd3d8.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 0582771.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit afa060f.

The original subject was "vmm: Remove duplicated
update_migration_progress in do_memory_iterations".

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 0b2ba9d.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 6c38ad2.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 8c86589.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

This also drops the payload from the match arm in
vm_post_migration_announce(), which was added later by 0f8e073 and is
therefore not part of the reverted diff. Without that the tree would not
compile.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 09c6399.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 0795c73.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 0aebb7b.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 5283a0c.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit a2abcf5.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit a925363.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 806b51b.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit c70dd77.

The upstream asynchronization series implements the same
functionality and is cherry-picked in this series instead.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 2a2f4e6.

The upstream asynchronization series implements the same
functionality and is cherry-picked in this series instead.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 9fa49fd.

The import lists also carry VmPostMigrationAnnounce, which stays.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 1688e55.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 838ea2e.

This commit cleaned up the fork's own migration worker. Upstream's
worker, which this series cherry-picks, already has the resulting
shape, so the commit is not re-applied.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit fe3709f.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 23414bb.

The post-migration announce call added later by 3655c61 stays.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit fbb64c8.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 9840545.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit 8bbea46.

The original subject was "ch-remote: wait for migration to finish by
querying migration progress".

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit d9a8206.

This commit builds on the fork's own migration worker, which this
series replaces with the upstream implementation. It is re-applied
on top of the upstream worker later in this series.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This reverts commit cf18742.

The upstream asynchronization series implements the same
functionality and is cherry-picked in this series instead.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
phip1611 and others added 29 commits September 24, 2026 13:18
This is part of the commit series to enable live updates about an
ongoing live migration. See the first commit for an introduction.

This commit actually brings all the functionality together. The first
version has the limitation that we populate the latest snapshot once per
memory iteration, although this is the most interesting part by far. In
a follow-up, we can make this more fine-grained.

We guarantee that as soon as SendMigration returns, migration progress
can be fetched as the underlying data source is populated.

[ Adapted to the upstream migration worker: the snapshot is initialized
  next to the VM info snapshot in vm_send_migration() and the failure is
  marked next to upstream's try_resume_vm_after_failed_migration(). The
  fork's own worker spawn code, which this commit also touched, is gone;
  instead the snapshot is marked as failed when spawning the upstream
  worker fails, which would otherwise leave it ongoing forever. For the
  same reason the snapshot is installed only after the last fallible
  step before the hand-over, the eventfd clone. ]

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This restores the fork-only `keep_alive` option of the send-migration
API call, which was dropped with the revert of commit
06a8b76 ("vmm: migration: switch to
non-blocking SendMigration call"). The rest of that commit is obsolete:
upstream's migration worker already dispatches the call without
blocking.

With `keep_alive=on` the VMM stays alive after a successful migration
so that management software can fetch the final migration progress. It
is then supposed to send a ShutdownVmm command.

The migration progress is now marked as finished once the VMM collects
the worker result, which is also where the option is evaluated. For
that, the worker hands the migration configuration back to the VMM
thread.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
We preserve the old behavior in ch-remote: SendMigration is blocking. A
new ´--dispatch` flag however ensures that one can just dispatch the
migration without waiting for it to finish (or fail).

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
`ch-remote send-migration` waits for the migration to finish again, so
its exit code no longer proves that the migration was dispatched. Pass
`--dispatch` to keep the dispatch semantics the test asserts on and to
keep the event-monitor assertions meaningful.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
These events happen fairly often now and are very spammy in the log.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
During live migration, VM ownership is moved away from the VMM thread.
To preserve guest-triggered reboot and shutdown lifecycle intent
across that ownership handover, we need a small lifecycle marker to
travel with the migrated VM state.

This change introduces `PostMigrationLifecycleEvent` and stores it in
`VmSnapshot` with `#[serde(default)]` for backward compatibility.
`Vm::snapshot()` now serializes the marker, and VM construction from a
snapshot restores it.

No control-loop behavior is changed in this commit. This is only the
data model/plumbing needed by follow-up commits.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
While a live migration is running, the migration worker owns the VM and
the VMM control loop cannot execute vm_reboot()/vmm_shutdown() directly.
Guest-triggered reset/exit events in that window currently hit
VmMigrating and fail.

This change makes the control loop consume reset/exit as before, but
when ownership is `MaybeVmOwnership::Migration` it postpones a
post-migration lifecycle intent instead of calling lifecycle handlers
directly.

The postponed state is first-event-wins and is cleared when a new send
migration starts, preventing stale lifecycle intent from leaking between
migrations.

This commit only introduces source-side postponing behavior and does not
yet apply or replay the postponed event.

[ Re-implemented on top of upstream's migration worker: the shared
  Arc<Mutex<Option<PostMigrationLifecycleEvent>>> is passed through
  MigrationWorker::spawn() instead of the fork's worker struct, the
  control loop matches on VmOwnership::Migration and thereby replaces
  upstream's two lifecycle TODO markers, and Vmm::send_migration() takes
  the shared state as an additional argument. The Arc/Mutex import in
  the worker module loses its kvm-and-x86_64 cfg, as the shared state is
  unconditional. ]

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
When a lifecycle event like reset or shutdown is postponed during
pre-copy, switch to downtime at the next iteration boundary.
This keeps the current iteration send intact and then transitions
into the existing graceful downtime path (`stop_vcpu_throttling()`,
`pause()`, final transfer, snapshot).

To keep behavior deterministic on source migration failure,
replay the postponed lifecycle event locally after ownership is
returned:
- VmReboot -> reset_evt
- VmmShutdown -> exit_evt

Postponed state is cleared on both success and failure paths to avoid
stale state across migrations.

[ Adapted to upstream's migration worker: the lifecycle event is
  replayed inside upstream's try_resume_vm_after_failed_migration(),
  right after it has given the VM back to the VMM. That keeps the
  original behaviour of replaying on every path that recovers the VM,
  which later also covers cancelled migrations. ]

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
[ The ownership match arm is adapted to upstream's VmOwnership enum. The
  todo!() in vm_cancel_migration() is filled in by the next commit, as
  in the original series. ]

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Introduce the minimal functionality required to support canceling a live
migration. This establishes the basic mechanism, while subsequent
commits will reduce the latency of cancellation so that migrations stop
more promptly.

Management software can and should wait for the migration to be actually
canceled via the vm.migration-progress endpoint.

[ Adapted to upstream's migration worker: the cancellation flag lives on
  MigrationWorkerHandle in migration/worker.rs and is therefore reached
  through VmOwnership::Migration instead of a separate Vmm field, and
  the cancelled VM is handed back with upstream's
  try_resume_vm_after_failed_migration(). ]

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This adds multiple points in the migration path where the migration can
be canceled early. The pre-copy phase is not addressed here and will
follow in the next commit!

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This commit reduces the delay in stopping memory transmission during the
pre-copy phase when a migration is canceled. The cancellation handling
is implemented in SendAdditionalConnections, which coordinates all
memory transmission threads.

In the cloud-hypervisor log, we can now even see that the cancellation
happens fairly quickly when in pre-copy phase with multiple
connections:

```
cloud-hypervisor:  11.263371s: <vmm> INFO:vmm/src/api/mod.rs:1147 -- API request event: VmCancelMigration ()
cloud-hypervisor:  11.263401s: <vmm> INFO:vmm/src/lib.rs:805 -- Will cancel ongoing live-migration
cloud-hypervisor:  11.263416s: <migration> INFO:vmm/src/lib.rs:2662 -- Cancelling migration now
cloud-hypervisor:  11.263568s: <migration> INFO:vmm/src/lib.rs:1667 -- Sending disconnect message to channels
cloud-hypervisor:  11.263594s: <migration> INFO:vmm/src/lib.rs:1677 -- Waiting for threads to finish
cloud-hypervisor:  11.302994s: <anonymous> INFO:vmm/src/lib.rs:1531 -- Sent 128 MiB via additional connection.
cloud-hypervisor:  11.303037s: <anonymous> INFO:vmm/src/lib.rs:1531 -- Sent 64 MiB via additional connection.
cloud-hypervisor:  11.303062s: <anonymous> INFO:vmm/src/lib.rs:1531 -- Sent 64 MiB via additional connection.
cloud-hypervisor:  11.303066s: <anonymous> INFO:vmm/src/lib.rs:1531 -- Sent 64 MiB via additional connection.
cloud-hypervisor:  11.303354s: <migration> INFO:vmm/src/lib.rs:1681 -- Threads finished
cloud-hypervisor:  11.303672s: <migration> ERROR:vmm/src/lib.rs:858 -- migrate error: Failed to continue the migration as it was cancelled
```

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
TL;DR: Major improvement for developers to see why a migration failed

The error model of Cloud Hypervisor leverages std::error::Error and
its source() function respectively to build chains of errors. This helps
to spot the root cause and see how a certain operation failed throughout
the stack. Therefore, the Display::fmt() impl of every error only
prints what failed "on its level", but never appends ": {0}", i.e., the
underling error's Display::fmt().

ch-remote and cloud-hypervisor can print these error chains nicely when
they exit - in the case of a migration, we do not exit however. The
solution is to print the error chain there as well to get more
meaningful errors.

As example:

old:
```
cloud-hypervisor:  62.820480s: <vmm> INFO:vmm/src/lib.rs:3382 -- VM migration check event

cloud-hypervisor:  62.820576s: <vmm> ERROR:vmm/src/lib.rs:3138 -- Migration failed: Failed to send migratable component snapshot
```

new:

```
cloud-hypervisor:  62.820480s: <vmm> INFO:vmm/src/lib.rs:3382 -- VM migration check event
cloud-hypervisor:  15.311401s: <vmm> ERROR:vmm/src/lib.rs:3110 -- Migration failed with the following chain of errors:
cloud-hypervisor:  15.311412s: <vmm> ERROR:vmm/src/lib.rs:3118 --   0: Failed to send migratable component snapshot
cloud-hypervisor:  15.311422s: <vmm> ERROR:vmm/src/lib.rs:3118 --   1: Error connecting to TCP socket
cloud-hypervisor:  15.311442s: <vmm> ERROR:vmm/src/lib.rs:3118 --   2: Connection refused (os error 111)
cloud-hypervisor:  62.820618s: <vmm> INFO:event_monitor/src/lib.rs:113 -- Event: source = vm event = migration-failed
```

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Move the migration workaround from the shared Exit path to GuestExit
and rename the postponed shutdown event to `VmShutdown`.

With --no-shutdown, guest-triggered shutdown must keep following the
guest exit path even when it is delayed until after migration
completion. This preserves the distinction between guest shutdown and
real VMM exit conditions.

The existing fatal exit path stays unchanged.

[ Adapted to upstream's migration worker: the postponement checks
  VmOwnership::Migration and replaces upstream's lifecycle TODO marker
  in the GuestExit arm of the control loop. ]

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
These are called very frequently by libvirt and spam the log.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Live migration can deadlock if the guest triggers a virtio device
activation while the migration worker owns the VM.

The failure shows up during boot and firmware, where the guest can
reset and reinitialize virtio devices while precopy is running. In the
failing case, the source log shows a pending virtio activation that
never completes:

  8.115833s _virtio-pci-net_0: Needs activation; returning barrier
  8.115854s vmm/src/vm.rs:464 -- Waiting for barrier
  24.875452s Entering downtime phase
  24.875481s stopping vcpu throttling thread
  ...
  vCPU thread did not respond in 10ms to signal - retrying
  vCPU thread did not respond in 20ms to signal - retrying
  ...
  thread 'throttle-vcpu' (1029) panicked
  ...
  Pause(Error signalling vCPUs: Timeout when waiting for signal
        to be acknowledged)

The vCPU blocks on the activation barrier and never reaches the normal
pause checkpoint. Later, migration enters downtime and stops the vCPU
throttle thread. In the failing case, that thread is still inside a
CpuManager::pause() call, which waits for every vCPU to acknowledge
the signal. The blocked vCPU never does, so the pause times out.

The VMM already receives ActivateVirtioDevices events during
migration, but it only drains pending activations when self.vm is in
MaybeVmOwnership::Vmm. Once vm_send_migration() moves the Vm into the
migration worker, self.vm becomes MaybeVmOwnership::Migration and the
event handler no longer has a path to call activate_virtio_devices().

Fix this by storing the DeviceManager inside
MaybeVmOwnership::Migration. This keeps just enough state on the VMM
thread to drain pending virtio activations while the migration worker
owns the Vm. The barrier logic stays unchanged. The VMM now releases
the same activation barrier during migration that it already released
before migration started.

This keeps the guest from getting stuck in the activation wait and
lets the later pause succeed.

[ Re-implemented on top of upstream's migration worker: the weak device
  manager reference is a field of VmOwnership::Migration instead of the
  fork's MigrationVmState helper struct, and the control loop's
  activation arm replaces upstream's virtio activation TODO marker. ]

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
This will help us in libvirt/ch to distinguish short send/receive races
from a real migration startup failure. By being able to check if the
migration has been successfully started (rather than just the migration
worker thread), we can improve the reliability of the start_migration()
logic in libvirt/ch.

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Streamlines the printing of the error message with upstream [0, 1].

[0] cloud-hypervisor@fcf2b5b
[1] cloud-hypervisor@2527020

On-behalf-of: SAP philipp.schuster@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
Migration threads may be left orphaned and keep the socket bound after
a migration has failed. Prevent this by signaling termination via the
`terminate_fd` in `ReceiveAdditionalConnections`'s `Drop` impl.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
The current behavior of joining the vCPU throttling thread towards the
end of a live migration is problematic when the live migration fails
because then auto-converge is no longer possible on a second attempt.

We fix this by instead resetting the throttling thread to its initial
state.

The throttling thread is now instead gracefully stopped by the
ThrottleThreadHandle's destructor which runs whenever the Vm instance
goes out of scope.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
When a live migration fails before all memory iterations have been
sent we also need to reset the throttling thread, otherwise the VM
continues existing on the migration source with throttled vCPUs.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
A single failing parallel migration connection can deadlock the
migration sender.

When the bounded send-channel is full,
`SendAdditionalConnections::cleanup()` silently drops the Disconnect
messages via the non-blocking `try_send`. The surviving workers drain
the channel and then block forever in `recv()`, while the main thread
blocks forever in `join()`. The VM stays stuck in the "migrating" state.

We fix this by sending the `Disconnect` messages with a blocking send,
ensuring the messages reach the migration workers.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
When a memory-sending worker fails, the migration thread calls
`SendAdditionalConnections::cleanup()`. Cleanup tries to enqueue one
`Disconnect` message per worker, but the bounded channel may still
contain `SendMemoryThreadMessage::Memory` values.

The surviving workers currently call `send_memory_ranges()` for each
such value, even though the migration has already failed. Skip that call
when `worker_error` is set. This consumes the queued values without
processing them and lets cleanup enqueue the `Disconnect` messages.

On-behalf-of: SAP leander.kohler@sap.com
Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de>
Signed-off-by: Philipp Schuster <philipp.schuster@cyberus-technology.de>
@phip1611
phip1611 force-pushed the gardenlinux-backport-asynchronization branch from 8fa1228 to a562ac2 Compare September 24, 2026 11:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants