virtio-devices: Replay in-order vhost-user queues after reconnect

When reconnecting to a vhost-user backend, Cloud Hypervisor keeps the
same queues and currently restarts them from avail_idx.

That can skip descriptors that were made available by the guest but not
completed before the old backend crashed. For queues where
VIRTIO_F_IN_ORDER was negotiated, used_idx is a safe completion
boundary, so reconnect can resume from used_idx and kick the queue if
work remains.

Do not do this when inflight tracking is active, because the backend
inflight state is the more precise recovery mechanism. Also leave queues
without VIRTIO_F_IN_ORDER on the existing avail_idx path, since used_idx
does not identify which descriptors completed for out-of-order devices.

Signed-off-by: Peter Delevoryas <pdel@meta.com>
This commit is contained in:
Peter Delevoryas
2026-07-15 14:08:15 -07:00
committed by Rob Bradford
parent c070d5dfa4
commit 5b68d7694d
2 changed files with 40 additions and 4 deletions

View File

@@ -143,6 +143,10 @@ pub enum Error {
MissingIrqFd, MissingIrqFd,
#[error("Failed getting the available index")] #[error("Failed getting the available index")]
GetAvailableIndex(#[source] QueueError), GetAvailableIndex(#[source] QueueError),
#[error("Failed getting the used index")]
GetUsedIndex(#[source] QueueError),
#[error("Failed to kick vhost-user vring")]
VhostUserKickVring(#[source] io::Error),
#[error("Migration is not supported by this vhost-user device")] #[error("Migration is not supported by this vhost-user device")]
MigrationNotSupported, MigrationNotSupported,
#[error("Failed creating memfd")] #[error("Failed creating memfd")]

View File

@@ -36,8 +36,8 @@ use vmm_sys_util::timerfd::TimerFd;
use super::{Error, Result, VhostUserState}; use super::{Error, Result, VhostUserState};
use crate::vhost_user::Inflight; use crate::vhost_user::Inflight;
use crate::{ use crate::{
GuestMemoryMmap, GuestRegionMmap, MmapRegion, VirtioInterrupt, VirtioInterruptType, GuestMemoryMmap, GuestRegionMmap, MmapRegion, VIRTIO_F_IN_ORDER, VirtioInterrupt,
get_host_address_range, VirtioInterruptType, get_host_address_range,
}; };
// Size of a dirty page for vhost-user. // Size of a dirty page for vhost-user.
@@ -369,6 +369,30 @@ impl VhostUserHandle {
) -> Result<()> { ) -> Result<()> {
self.set_protocol_features_vhost_user(acked_features, acked_protocol_features)?; self.set_protocol_features_vhost_user(acked_features, acked_protocol_features)?;
let (vring_bases, notification_needed) =
if inflight.is_none() && acked_features & (1u64 << VIRTIO_F_IN_ORDER) != 0 {
// With in-order processing, used_idx is a contiguous completion
// boundary. Replay descriptors after it when inflight tracking is
// not available to recover them more precisely.
let mut vring_bases = Vec::with_capacity(queues.len());
let mut notification_needed = Vec::with_capacity(queues.len());
for (_, queue, _) in queues {
let used_idx = queue
.used_idx(mem, Ordering::Acquire)
.map_err(Error::GetUsedIndex)?
.0;
let avail_idx = queue
.avail_idx(mem, Ordering::Acquire)
.map_err(Error::GetAvailableIndex)?
.0;
vring_bases.push(u64::from(used_idx));
notification_needed.push(used_idx != avail_idx);
}
(Some(vring_bases), notification_needed)
} else {
(None, vec![false; queues.len()])
};
self.setup_vhost_user( self.setup_vhost_user(
mem, mem,
queues, queues,
@@ -376,8 +400,16 @@ impl VhostUserHandle {
acked_features, acked_features,
backend_req_handler, backend_req_handler,
inflight, inflight,
None, vring_bases.as_deref(),
) )?;
for ((_, _, queue_evt), notification_needed) in queues.iter().zip(notification_needed) {
if notification_needed {
queue_evt.write(1).map_err(Error::VhostUserKickVring)?;
}
}
Ok(())
} }
pub fn connect_vhost_user( pub fn connect_vhost_user(