mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
virtio-devices: Resume vhost-user devices after snapshot
For vhost-user devices, a snapshot involved the vrings to be stopped, but they couldn't be resumed. This commit aims at fixing this by saving the vrings base so that it can be safely reset after the snapshot has succeeded. This will allow VMs where snapshots are taken from internal CH implementation and from an offload daemon to be resumed once the snapshot is complete. Signed-off-by: Sebastien Boeuf <sboeuf@meta.com> Assisted-by: Claude:claude-opus-4-8
This commit is contained in:
@@ -111,6 +111,8 @@ pub enum ActivateError {
|
||||
BadActivate,
|
||||
#[error("Failed to clone EventFd")]
|
||||
CloneEventFd(#[source] io::Error),
|
||||
#[error("Failed to create EventFd")]
|
||||
CreateEventFd(#[source] io::Error),
|
||||
#[error("Failed to spawn thread")]
|
||||
ThreadSpawn(#[source] io::Error),
|
||||
#[error("Failed to setup vhost-user-fs daemon")]
|
||||
|
||||
@@ -265,6 +265,7 @@ pub const DEFAULT_VIRTIO_FEATURES: u64 = (1 << VIRTIO_F_RING_INDIRECT_DESC)
|
||||
|
||||
const HUP_CONNECTION_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 1;
|
||||
const BACKEND_REQ_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 2;
|
||||
const RESUME_EVENT: u16 = EPOLL_HELPER_EVENT_LAST + 3;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Inflight {
|
||||
@@ -287,6 +288,7 @@ pub struct VhostUserEpollHandler<S: VhostUserFrontendReqHandler> {
|
||||
pub inflight: Option<Inflight>,
|
||||
/// Flag set by the worker when the vhost-user backend is no longer reachable.
|
||||
pub disconnected: Arc<AtomicBool>,
|
||||
pub resume_evt: EventFd,
|
||||
}
|
||||
|
||||
impl<S: VhostUserFrontendReqHandler> VhostUserEpollHandler<S> {
|
||||
@@ -306,6 +308,8 @@ impl<S: VhostUserFrontendReqHandler> VhostUserEpollHandler<S> {
|
||||
helper.add_event(backend_req_handler.as_raw_fd(), BACKEND_REQ_EVENT)?;
|
||||
}
|
||||
|
||||
helper.add_event(self.resume_evt.as_raw_fd(), RESUME_EVENT)?;
|
||||
|
||||
helper.run(paused, paused_sync, self)?;
|
||||
|
||||
Ok(())
|
||||
@@ -415,6 +419,26 @@ impl<S: VhostUserFrontendReqHandler> EpollHelperHandler for VhostUserEpollHandle
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
RESUME_EVENT => {
|
||||
let _ = self.resume_evt.read();
|
||||
self.vu
|
||||
.lock()
|
||||
.unwrap()
|
||||
.resume(
|
||||
&self.mem.memory(),
|
||||
&self.queues,
|
||||
self.virtio_interrupt.as_ref(),
|
||||
self.acked_features,
|
||||
&self.backend_req_handler,
|
||||
self.inflight.as_mut(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
EpollHelperError::HandleEvent(anyhow!(
|
||||
"failed to resume vhost-user backend for socket {}: {e:?}",
|
||||
self.socket_path
|
||||
))
|
||||
})
|
||||
}
|
||||
_ => Err(EpollHelperError::HandleEvent(anyhow!(
|
||||
"Unknown event for vhost-user thread"
|
||||
))),
|
||||
@@ -472,6 +496,7 @@ pub struct VhostUserCommon {
|
||||
pub disconnected: Arc<AtomicBool>,
|
||||
saved_dirty_log: Option<MemoryRangeTable>,
|
||||
dirty_logging: bool,
|
||||
resume_evt: Option<EventFd>,
|
||||
}
|
||||
|
||||
impl VhostUserCommon {
|
||||
@@ -527,6 +552,13 @@ impl VhostUserCommon {
|
||||
)
|
||||
.map_err(ActivateError::VhostUserSetup)?;
|
||||
|
||||
let resume_evt = EventFd::new(libc::EFD_NONBLOCK).map_err(ActivateError::CreateEventFd)?;
|
||||
self.resume_evt = Some(
|
||||
resume_evt
|
||||
.try_clone()
|
||||
.map_err(ActivateError::CloneEventFd)?,
|
||||
);
|
||||
|
||||
Ok(VhostUserEpollHandler {
|
||||
vu: vu.clone(),
|
||||
mem,
|
||||
@@ -541,6 +573,7 @@ impl VhostUserCommon {
|
||||
backend_req_handler,
|
||||
inflight,
|
||||
disconnected: self.disconnected.clone(),
|
||||
resume_evt,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -706,31 +739,22 @@ impl VhostUserCommon {
|
||||
}
|
||||
|
||||
fn resume_internal(&mut self) -> result::Result<(), MigratableError> {
|
||||
// Skip the resume_vhost_user call if the backend is disconnected. Process the queue
|
||||
// interrupts to kick any paused workers.
|
||||
if self.disconnected.load(Ordering::Relaxed) {
|
||||
return Err(MigratableError::DeviceDisconnected(
|
||||
self.socket_path.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(vu) = &self.vu
|
||||
&& let Err(e) = vu.lock().unwrap().resume_vhost_user()
|
||||
{
|
||||
if e.is_transport_lost() {
|
||||
self.disconnected.store(true, Ordering::Relaxed);
|
||||
return Err(MigratableError::DeviceDisconnected(
|
||||
self.socket_path.clone(),
|
||||
));
|
||||
}
|
||||
let Some(evt) = &self.resume_evt else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
return Err(MigratableError::Resume(anyhow!(
|
||||
"Error resuming vhost-user backend for socket {}: {e:?}",
|
||||
evt.write(1).map_err(|e| {
|
||||
MigratableError::Resume(anyhow!(
|
||||
"Error signaling vhost-user resume for socket {}: {e}",
|
||||
self.socket_path
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resume(&mut self) -> result::Result<(), MigratableError> {
|
||||
|
||||
@@ -67,6 +67,7 @@ pub struct VhostUserHandle {
|
||||
acked_features: u64,
|
||||
vrings_info: Option<Vec<VringInfo>>,
|
||||
queue_indexes: Vec<u16>,
|
||||
vring_bases: Option<Vec<u64>>,
|
||||
}
|
||||
|
||||
impl VhostUserHandle {
|
||||
@@ -172,6 +173,9 @@ impl VhostUserHandle {
|
||||
return Err(Error::VringBasesCountMismatch(bases.len(), queues.len()));
|
||||
}
|
||||
|
||||
// May run more than once on the same handle (resume after snapshot)
|
||||
self.queue_indexes.clear();
|
||||
|
||||
self.vu
|
||||
.set_features(acked_features)
|
||||
.map_err(Error::VhostUserSetFeatures)?;
|
||||
@@ -427,6 +431,7 @@ impl VhostUserHandle {
|
||||
acked_features: 0,
|
||||
vrings_info: None,
|
||||
queue_indexes: Vec::new(),
|
||||
vring_bases: None,
|
||||
};
|
||||
vhost_user
|
||||
.vu
|
||||
@@ -485,6 +490,7 @@ impl VhostUserHandle {
|
||||
acked_features: 0,
|
||||
vrings_info: None,
|
||||
queue_indexes: Vec::new(),
|
||||
vring_bases: None,
|
||||
})
|
||||
.map_err(Error::VhostUserConnect);
|
||||
|
||||
@@ -580,7 +586,7 @@ impl VhostUserHandle {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resume_vhost_user(&mut self) -> Result<()> {
|
||||
fn resume_vhost_user(&mut self) -> Result<()> {
|
||||
if self.ready {
|
||||
self.enable_vhost_user_vrings(self.queue_indexes.clone(), true)?;
|
||||
}
|
||||
@@ -641,9 +647,39 @@ impl VhostUserHandle {
|
||||
.check_device_state()
|
||||
.map_err(Error::VhostUserCheckDeviceState)?;
|
||||
|
||||
// Store the bases in case we need to resume after snapshot
|
||||
self.vring_bases = Some(vring_bases.clone());
|
||||
|
||||
Ok((state, vring_bases))
|
||||
}
|
||||
|
||||
/// Resume the vhost-user backend from the device thread.
|
||||
/// Initialize the vrings from the bases captured earlier, or simply
|
||||
/// enable them if they haven't been stopped.
|
||||
pub fn resume<S: VhostUserFrontendReqHandler>(
|
||||
&mut self,
|
||||
mem: &GuestMemoryMmap,
|
||||
queues: &[(u16, Queue, EventFd)],
|
||||
virtio_interrupt: &dyn VirtioInterrupt,
|
||||
acked_features: u64,
|
||||
backend_req_handler: &Option<FrontendReqHandler<S>>,
|
||||
inflight: Option<&mut Inflight>,
|
||||
) -> Result<()> {
|
||||
let Some(vring_bases) = self.vring_bases.take() else {
|
||||
return self.resume_vhost_user();
|
||||
};
|
||||
|
||||
self.setup_vhost_user(
|
||||
mem,
|
||||
queues,
|
||||
virtio_interrupt,
|
||||
acked_features,
|
||||
backend_req_handler,
|
||||
inflight,
|
||||
Some(&vring_bases),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn restore_state<C>(&mut self, state: &VhostUserState<C>) -> Result<()> {
|
||||
state.validate()?;
|
||||
if let Some(backend_state) = &state.backend_state {
|
||||
|
||||
Reference in New Issue
Block a user