diff --git a/virtio-devices/src/lib.rs b/virtio-devices/src/lib.rs index 7b5374b9d..4d2db1129 100644 --- a/virtio-devices/src/lib.rs +++ b/virtio-devices/src/lib.rs @@ -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")] diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index d9eb9f73f..693f3033f 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -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 { pub inflight: Option, /// Flag set by the worker when the vhost-user backend is no longer reachable. pub disconnected: Arc, + pub resume_evt: EventFd, } impl VhostUserEpollHandler { @@ -306,6 +308,8 @@ impl VhostUserEpollHandler { 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 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, saved_dirty_log: Option, dirty_logging: bool, + resume_evt: Option, } 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> { diff --git a/virtio-devices/src/vhost_user/vu_common_ctrl.rs b/virtio-devices/src/vhost_user/vu_common_ctrl.rs index 6e8f4d9d6..e2f7b5572 100644 --- a/virtio-devices/src/vhost_user/vu_common_ctrl.rs +++ b/virtio-devices/src/vhost_user/vu_common_ctrl.rs @@ -67,6 +67,7 @@ pub struct VhostUserHandle { acked_features: u64, vrings_info: Option>, queue_indexes: Vec, + vring_bases: Option>, } 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( + &mut self, + mem: &GuestMemoryMmap, + queues: &[(u16, Queue, EventFd)], + virtio_interrupt: &dyn VirtioInterrupt, + acked_features: u64, + backend_req_handler: &Option>, + 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(&mut self, state: &VhostUserState) -> Result<()> { state.validate()?; if let Some(backend_state) = &state.backend_state {