hypervisor: fix virtio device livelock on snapshot restore by kicking queues and injecting interrupt on resume#1084
Conversation
A restored virtqueue can already contain pending descriptors when the VM resumes. Before this change, the worker thread was unparked and then waited for a fresh queue eventfd signal. That is normally fine, but not when the queue was already non-empty at snapshot time. The virtqueue state lives in guest memory and is restored, but the original host-side queue eventfd signal is not persistent snapshot state. If the guest already notified the queue before the snapshot, it may not notify it again after resume. That can leave the worker idle while the guest is still waiting for the pending request to complete. In one observed case, this stalled a virtio-blk flush during early boot after snapshot/restore. We mitigate this in the shared `VirtioCommon` resume path. `VirtioCommon` retains cloned queue eventfds for activated virtqueues and signals each of them once on resume after unparking the worker threads. Keep virtio-net on its existing special-case path: it resumes worker threads without signaling queue eventfds so the `driver_awake` workaround remains intact until the guest performs a real notify. On-behalf-of: SAP leander.kohler@sap.com Signed-off-by: Leander Kohler <leander.kohler@cyberus-technology.de> (cherry picked from commit f7c3b775819f181284fe1272543a0f196f029ad1) Signed-off-by: Songqian Li <sionli@tencent.com>
This will wake up the guest and avoid a livelock situation by ensuring that it will process any pending queues on its side. Signed-off-by: Rob Bradford <rbradford@meta.com> (cherry picked from commit abf32187f342828901fd405e0cc07e3f1a8eb2a9) Signed-off-by: Songqian Li <sionli@tencent.com>
Now on the generic restore path the worker thread is notified on the events and also the guest is notified via the interrupt. This avoids the same "livelock" situation that required this "driver_awake" workaround when restoring the net device. Signed-off-by: Rob Bradford <rbradford@meta.com> (cherry picked from commit 5d02dc7c1ddde0b53fcba70be2cce979c9efe50b) Signed-off-by: Songqian Li <sionli@tencent.com>
Review: PR #1084 — Fix virtio device livelock on snapshot restoreOverall verdict: ✅ Ship it. The change is correct, well-documented, and follows upstream cloud-hypervisor fixes. The logic is sound and the What the PR doesThree cherry-picked patches that fix a snapshot/restore livelock in the shared
Correctness verificationAll devices benefit. Every virtio device type in this codebase calls
Real queue index vs. position. The code correctly stores
Eventfd cloning is sound.
Minor observations (not blockers)
Suggested inline commentNone — the code is well-commented, especially the block explaining why real queue indices must be used rather than positional indices. That comment would be easy to leave out, and including it shows good engineering judgment. |
|
Just curious, where did these commit hashes come from? For example, the third commit should be 72fc0976f113284ee229bc657926d3b712f74e6a. |
VirtioCommon::resume signals each activated queue's eventfd and then
injects an interrupt so a restored guest driver does not livelock on a
used-ring completion that was lost across snapshot/restore. The
interrupt loop, however, used the position in `queue_evts` as the queue
index:
for i in 0..self.queue_evts.len() {
interrupt_cb.trigger(VirtioInterruptType::Queue(i as u16)).ok();
}
`VirtioInterruptType::Queue(x)` is defined to carry the real virtio
queue index, and `VirtioInterruptMsix` uses it to look up
`queues_vectors`, which is sized to `total_queues` and populated by the
guest driver at real-index positions. When the set of ready queues is
not contiguous from 0, the position in `queue_evts` differs from the
real queue index. The mismatched index then either lands on a
`VIRTQ_MSI_NO_VECTOR` slot (silently dropping the interrupt) or on an
unrelated queue's vector.
The most realistic trigger is multi-queue virtio-net with a control
queue: when the guest enables only a subset of the queue pairs, the
ready set looks like {0, 1, ..., 2k-1, 2N} where 2N is the control
queue's real index. On resume the loop stops at position 2k and never
touches index 2N, so the control queue interrupt is dropped and the
driver can hang waiting for a control command completion. This is
especially likely right after live migration, when the announce
(gratuitous ARP/RARP) path exercises the control queue.
Preserve the real queue index alongside the eventfd by changing
`VirtioCommon::queue_evts` from `Vec<EventFd>` to
`Vec<(u16, EventFd)>`. `activate` fills the tuple from the
`(queue_index, Queue, EventFd)` slice it already receives, and `resume`
triggers `VirtioInterruptType::Queue(real_index)` for every activated
queue. Ready queues that are contiguous from 0 continue to work
unchanged (their real index equals their position).
`VirtioCommon` keeps `#[derive(Default)]` and `Vec<(u16, EventFd)>` has
an empty default, so the ~15 call sites that build `VirtioCommon` with
`..Default::default()` need no changes.
The upstream cloud-hypervisor PR #8004 that introduced this resume path
carries the same defect; this fix applies to that upstream code as
well.
Reported-by: Yi Wang <foxywang@tencent.com>
Signed-off-by: Songqian Li <sionli@tencent.com>
From this PR: cloud-hypervisor/cloud-hypervisor#8004 |
Problem
After a VM is snapshotted and later restored, virtio devices can wedge in a livelock:
kick.Both sides end up waiting for each other. In practice this reproduces as:
virtio-blkflush stalls during early boot after snapshot/restore.virtio-nettraffic freezes after resume unless the olddriver_awakeworkaround is used.Fix
Cherry-pick three upstream cloud-hypervisor patches that fix the resume-path kick properly in the shared
VirtioCommonlayer, and then retire the ad-hocdriver_awakeworkaround invirtio-net:virtio-devices: signal activated queue eventfds on resumeVirtioCommonnow keeps clones of the activated queue eventfds and signals each of them once on resume, right after unparking the worker threads. This guarantees the worker re-checks the queue even if the guest already kicked before the snapshot.(cherry-picked from cloud-hypervisor
f7c3b775819f)virtio-devices: trigger interrupt into guest on resumeAlso inject a used-ring interrupt into the guest on resume, so the guest re-checks any request it thought was in flight instead of waiting forever for a notification that already happened before the snapshot.
(cherry-picked from cloud-hypervisor
abf32187f342)virtio-devices: net: Remove "driver_awake" workaround for restoreWith the generic resume path above, virtio-net no longer needs its own device-specific
driver_awakehack — the shared kick + interrupt already covers it.(cherry-picked from cloud-hypervisor
5d02dc7c1ddd)Scope
hypervisor/virtio-devices/src/device.rs,hypervisor/virtio-devices/src/net.rs.resume()path used by snapshot/restore.Signed-off-by/(cherry picked from commit ...)trailers preserved.Testing
driver_awakeworkaround.