From 3e3008f3657cc7a2db3da75977a91f7e0d7e92c9 Mon Sep 17 00:00:00 2001 From: Leander Kohler Date: Tue, 14 Apr 2026 08:56:01 +0200 Subject: [PATCH] virtio-devices: signal activated queue eventfds on resume 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 --- virtio-devices/src/device.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/virtio-devices/src/device.rs b/virtio-devices/src/device.rs index f0673f561..96a40a4da 100644 --- a/virtio-devices/src/device.rs +++ b/virtio-devices/src/device.rs @@ -13,6 +13,7 @@ use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::{Arc, Barrier}; use std::thread; +use anyhow::anyhow; use libc::EFD_NONBLOCK; use log::{error, info, warn}; use virtio_queue::Queue; @@ -215,6 +216,7 @@ pub struct VirtioCommon { pub paused_sync: Option>, pub epoll_threads: Option>>, pub queue_sizes: Vec, + pub queue_evts: Vec, pub device_type: u32, pub min_queues: u16, pub access_platform: Option>, @@ -252,6 +254,21 @@ impl VirtioCommon { return Err(ActivateError::BadActivate); } + // Do not retain virtio-net queue eventfds here. Signaling them on + // resume would break its `driver_awake` workaround. + self.queue_evts = match VirtioDeviceType::from(self.device_type) { + VirtioDeviceType::Net => Vec::new(), + _ => queues + .iter() + .map(|(_, _, queue_evt)| { + queue_evt.try_clone().map_err(|e| { + error!("failed cloning queue EventFd: {e}"); + ActivateError::BadActivate + }) + }) + .collect::, _>>()?, + }; + let kill_evt = EventFd::new(EFD_NONBLOCK).map_err(|e| { error!("failed creating kill EventFd: {e}"); ActivateError::BadActivate @@ -272,6 +289,8 @@ impl VirtioCommon { } pub fn reset(&mut self) -> Option> { + self.queue_evts.clear(); + // We first must resume the virtio thread if it was paused. if self.pause_evt.take().is_some() { self.resume().ok()?; @@ -355,6 +374,16 @@ impl Pausable for VirtioCommon { } } + // Signal each activated queue eventfd so workers process restored queues + // that may already contain pending requests. + for queue_evt in &self.queue_evts { + queue_evt.write(1).map_err(|e| { + MigratableError::Resume(anyhow!( + "Could not notify restored virtio worker on resume: {e}" + )) + })?; + } + Ok(()) } }