From 4f60379142087323062ef4328f2d1a7344efb291 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 8 May 2026 17:26:46 -0700 Subject: [PATCH] virtio-devices: vhost_user: classify disconnected backend errors Add two explicit disconnected-backend error paths before wiring them into the call sites. MigratableError::DeviceDisconnected is the lifecycle sentinel for operations that were skipped because a component is already known to be disconnected. It lets the caller log and continue without treating it as a VMM-fatal condition. Error::BackendDisconnected is the vhost-user-local error used when VhostUserCommon refuses to call a backend after its disconnected flag is set. The transport classifier treats socket close/reset/EOF and vhost-user partial-message/disconnected cases as transport loss, while backend NACKs, invalid protocol state, and retry-able socket errors remain ordinary operation failures. Signed-off-by: Dylan Reid --- virtio-devices/src/vhost_user/mod.rs | 71 +++++++++++++++++++++++++++- vm-migration/src/lib.rs | 3 ++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/virtio-devices/src/vhost_user/mod.rs b/virtio-devices/src/vhost_user/mod.rs index 20c0b3c1f..2f85f3f66 100644 --- a/virtio-devices/src/vhost_user/mod.rs +++ b/virtio-devices/src/vhost_user/mod.rs @@ -1,6 +1,7 @@ // Copyright 2019 Intel Corporation. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +use std::io::ErrorKind; use std::ops::Deref; use std::os::unix::io::AsRawFd; use std::sync::atomic::{AtomicBool, Ordering}; @@ -16,7 +17,7 @@ use vhost::Error as VhostError; use vhost::vhost_user::message::{ VhostUserInflight, VhostUserProtocolFeatures, VhostUserVirtioFeatures, }; -use vhost::vhost_user::{FrontendReqHandler, VhostUserFrontendReqHandler}; +use vhost::vhost_user::{Error as VhostUserError, FrontendReqHandler, VhostUserFrontendReqHandler}; use virtio_queue::{Error as QueueError, Queue}; use vm_memory::guest_memory::Error as MmapError; use vm_memory::mmap::MmapRegionError; @@ -73,6 +74,8 @@ pub enum Error { VhostUserOpen(#[source] VhostError), #[error("Connection to socket failed")] VhostUserConnect(#[source] VhostError), + #[error("Backend disconnected for socket {0}")] + BackendDisconnected(String), #[error("Get features failed")] VhostUserGetFeatures(#[source] VhostError), #[error("Get queue max number failed")] @@ -176,6 +179,72 @@ pub enum Error { } type Result = std::result::Result; +fn io_error_is_connection_lost(error: &io::Error) -> bool { + matches!( + error.kind(), + ErrorKind::BrokenPipe + | ErrorKind::ConnectionAborted + | ErrorKind::ConnectionReset + | ErrorKind::NotConnected + | ErrorKind::UnexpectedEof + ) +} + +fn vhost_user_error_is_transport_lost(error: &VhostUserError) -> bool { + match error { + VhostUserError::Disconnected + | VhostUserError::PartialMessage + | VhostUserError::SocketBroken(_) + | VhostUserError::SocketConnect(_) => true, + VhostUserError::SocketError(e) => io_error_is_connection_lost(e), + _ => false, + } +} + +fn vhost_error_is_transport_lost(error: &VhostError) -> bool { + match error { + VhostError::VhostUserProtocol(e) => vhost_user_error_is_transport_lost(e), + VhostError::IOError(e) => io_error_is_connection_lost(e), + _ => false, + } +} + +impl Error { + fn is_transport_lost(&self) -> bool { + match self { + Error::VhostUserConnect(_) | Error::BackendDisconnected(_) => true, + Error::VhostUserCreateFrontend(e) + | Error::VhostUserOpen(e) + | Error::VhostUserGetFeatures(e) + | Error::VhostUserGetQueueMaxNum(e) + | Error::VhostUserGetProtocolFeatures(e) + | Error::VhostUserGetVringBase(e) + | Error::VhostUserSetOwner(e) + | Error::VhostUserResetOwner(e) + | Error::VhostUserSetFeatures(e) + | Error::VhostUserSetProtocolFeatures(e) + | Error::VhostUserSetMemTable(e) + | Error::VhostUserSetVringNum(e) + | Error::VhostUserSetVringAddr(e) + | Error::VhostUserSetVringBase(e) + | Error::VhostUserSetVringCall(e) + | Error::VhostUserSetVringKick(e) + | Error::VhostUserSetVringEnable(e) + | Error::VhostUserSetBackendRequestFd(e) + | Error::VhostUserAddMemReg(e) + | Error::VhostUserGetConfig(e) + | Error::VhostUserSetConfig(e) + | Error::VhostUserGetInflight(e) + | Error::VhostUserSetInflight(e) + | Error::VhostUserSetLogBase(e) + | Error::VhostUserSetDeviceStateFd(e) + | Error::VhostUserCheckDeviceState(e) => vhost_error_is_transport_lost(e), + Error::FrontendReqHandlerCreation(e) => vhost_user_error_is_transport_lost(e), + _ => false, + } + } +} + pub const DEFAULT_VIRTIO_FEATURES: u64 = (1 << VIRTIO_F_RING_INDIRECT_DESC) | (1 << VIRTIO_F_RING_EVENT_IDX) | (1 << VIRTIO_F_VERSION_1) diff --git a/vm-migration/src/lib.rs b/vm-migration/src/lib.rs index 0faedf285..125d762bf 100644 --- a/vm-migration/src/lib.rs +++ b/vm-migration/src/lib.rs @@ -92,6 +92,9 @@ pub enum MigratableError { #[error("Failed to release a disk lock")] UnlockError(#[source] anyhow::Error), + + #[error("Lifecycle operation skipped for disconnected component {0}")] + DeviceDisconnected(String), } /// A Pausable component can be paused and resumed.