mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
virtio-devices: Add VirtioCommon::spawn_worker helper
Implement a method on VirtioCommon for spawning the worker thread. This method also handles storing the handle to the thread and triggering a reset on the device if thread spawning fails. The addition of this helper replaces a repeated pattern across virtio and vhost-user devices but also ensures correct cleanup when spawning multiple threads for a device with multiple queues. Assisted-by: Claude:Opus-4.7 Signed-off-by: Rob Bradford <rbradford@meta.com>
This commit is contained in:
@@ -16,6 +16,7 @@ use std::thread;
|
|||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use libc::EFD_NONBLOCK;
|
use libc::EFD_NONBLOCK;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
|
use seccompiler::SeccompAction;
|
||||||
use virtio_bindings::virtio_config::VIRTIO_F_ACCESS_PLATFORM;
|
use virtio_bindings::virtio_config::VIRTIO_F_ACCESS_PLATFORM;
|
||||||
use virtio_queue::Queue;
|
use virtio_queue::Queue;
|
||||||
use vm_device::UserspaceMapping;
|
use vm_device::UserspaceMapping;
|
||||||
@@ -24,6 +25,9 @@ use vm_migration::{MigratableError, Pausable};
|
|||||||
use vm_virtio::{AccessPlatform, VirtioDeviceType};
|
use vm_virtio::{AccessPlatform, VirtioDeviceType};
|
||||||
use vmm_sys_util::eventfd::EventFd;
|
use vmm_sys_util::eventfd::EventFd;
|
||||||
|
|
||||||
|
use crate::epoll_helper::EpollHelperError;
|
||||||
|
use crate::seccomp_filters::Thread;
|
||||||
|
use crate::thread_helper::spawn_virtio_thread;
|
||||||
use crate::{
|
use crate::{
|
||||||
ActivateError, ActivateResult, Error, GuestMemoryMmap, GuestRegionMmap, MmapRegion,
|
ActivateError, ActivateResult, Error, GuestMemoryMmap, GuestRegionMmap, MmapRegion,
|
||||||
VIRTIO_F_RING_INDIRECT_DESC,
|
VIRTIO_F_RING_INDIRECT_DESC,
|
||||||
@@ -329,6 +333,41 @@ impl VirtioCommon {
|
|||||||
self.interrupt_cb = None;
|
self.interrupt_cb = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawn a worker, push its handle into `self.epoll_threads`, and on
|
||||||
|
/// spawn failure run `self.reset()` to join prior workers.
|
||||||
|
#[expect(clippy::too_many_arguments)]
|
||||||
|
pub fn spawn_worker<F>(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
seccomp_action: &SeccompAction,
|
||||||
|
thread_type: Thread,
|
||||||
|
exit_evt: &EventFd,
|
||||||
|
device_status: Arc<AtomicU8>,
|
||||||
|
interrupt_cb: Arc<dyn VirtioInterrupt>,
|
||||||
|
f: F,
|
||||||
|
) -> Result<(), ActivateError>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Result<(), EpollHelperError> + Send + 'static,
|
||||||
|
{
|
||||||
|
let mut threads = self.epoll_threads.take().unwrap_or_default();
|
||||||
|
let res = spawn_virtio_thread(
|
||||||
|
name,
|
||||||
|
seccomp_action,
|
||||||
|
thread_type,
|
||||||
|
&mut threads,
|
||||||
|
exit_evt,
|
||||||
|
device_status,
|
||||||
|
interrupt_cb,
|
||||||
|
f,
|
||||||
|
);
|
||||||
|
self.epoll_threads = Some(threads);
|
||||||
|
if let Err(e) = res {
|
||||||
|
self.reset();
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn trigger_interrupt(&self, int_type: VirtioInterruptType) -> std::io::Result<()> {
|
pub fn trigger_interrupt(&self, int_type: VirtioInterruptType) -> std::io::Result<()> {
|
||||||
if let Some(interrupt_cb) = &self.interrupt_cb {
|
if let Some(interrupt_cb) = &self.interrupt_cb {
|
||||||
interrupt_cb.trigger(int_type)
|
interrupt_cb.trigger(int_type)
|
||||||
@@ -432,3 +471,74 @@ impl Pausable for VirtioCommon {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod unit_tests {
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use vmm_sys_util::eventfd::EFD_NONBLOCK;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct NoopInterrupt;
|
||||||
|
impl VirtioInterrupt for NoopInterrupt {
|
||||||
|
fn trigger(&self, _: VirtioInterruptType) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn set_notifier(
|
||||||
|
&self,
|
||||||
|
_: u32,
|
||||||
|
_: Option<EventFd>,
|
||||||
|
_: &dyn hypervisor::Vm,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_common_with_kill_evt() -> (VirtioCommon, EventFd) {
|
||||||
|
let kill_evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||||
|
let kill_evt_clone = kill_evt.try_clone().unwrap();
|
||||||
|
let common = VirtioCommon {
|
||||||
|
kill_evt: Some(kill_evt),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
(common, kill_evt_clone)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spawn_worker_appends_to_epoll_threads() {
|
||||||
|
let (mut common, kill_evt_clone) = make_common_with_kill_evt();
|
||||||
|
let started = Arc::new(AtomicUsize::new(0));
|
||||||
|
let started_clone = started.clone();
|
||||||
|
|
||||||
|
let exit_evt = EventFd::new(EFD_NONBLOCK).unwrap();
|
||||||
|
let status = Arc::new(AtomicU8::new(0));
|
||||||
|
|
||||||
|
common
|
||||||
|
.spawn_worker(
|
||||||
|
"test",
|
||||||
|
&SeccompAction::Allow,
|
||||||
|
Thread::VirtioBlock,
|
||||||
|
&exit_evt,
|
||||||
|
status,
|
||||||
|
Arc::new(NoopInterrupt),
|
||||||
|
move || {
|
||||||
|
started_clone.fetch_add(1, Ordering::SeqCst);
|
||||||
|
let _ = kill_evt_clone.read();
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let threads = common.epoll_threads.as_ref().expect("epoll_threads set");
|
||||||
|
assert_eq!(threads.len(), 1);
|
||||||
|
|
||||||
|
// reset() joins the worker; this confirms the spawn_worker
|
||||||
|
// -> reset() chain (used on spawn failure) actually drains
|
||||||
|
// a real, running worker rather than dropping it detached.
|
||||||
|
common.reset();
|
||||||
|
assert!(common.epoll_threads.is_none());
|
||||||
|
assert!(common.kill_evt.is_none());
|
||||||
|
assert_eq!(started.load(Ordering::SeqCst), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user