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 <dgreid@fb.com>
This commit is contained in:
Dylan Reid
2026-05-08 17:26:46 -07:00
committed by Bo Chen
parent 45fdc276fe
commit 4f60379142
2 changed files with 73 additions and 1 deletions

View File

@@ -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<T> = std::result::Result<T, Error>;
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)

View File

@@ -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.