Skip to content

Commit 2715fb4

Browse files
authored
virtio: remove per-work Arc<Mutex> clone from queue completion path (#3173)
Every `VirtioQueueCallbackWork` currently clones an `Arc<Mutex<VirtioQueueUsedHandler>>` so that it can independently call `complete()` on itself. This means every descriptor processed by every virtio device pays for an atomic ref-count increment/decrement pair plus a mutex lock on the completion path—overhead that adds up on high-throughput devices like virtio-net and virtio-blk. This PR moves completion responsibility from the work item to the queue: callers now invoke `VirtioQueue::complete(work, bytes_written)` instead of `work.complete(bytes_written)`. Because the queue already owns the used-ring handler, the `Arc<Mutex>` clone per work item, the `completed` tracking flag, and the `Drop` guard on `VirtioQueue` are all eliminated. All in-tree virtio device crates are updated to the new pattern. For virtio-vsock, the refactor also fixes a latent bug where `write_packet` could early-return on error without completing the work item; the caller now always completes it.
1 parent 148f98d commit 2715fb4

11 files changed

Lines changed: 273 additions & 253 deletions

File tree

vm/devices/virtio/virtio/src/common.rs

Lines changed: 34 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use guestmem::GuestMemoryError;
1919
use inspect::Inspect;
2020
use pal_async::wait::PolledWait;
2121
use pal_event::Event;
22-
use parking_lot::Mutex;
2322
use std::io::Error;
2423
use std::pin::Pin;
2524
use std::sync::Arc;
@@ -98,67 +97,21 @@ fn read_from_payload_at_offset(
9897
Ok(read_bytes)
9998
}
10099

101-
#[derive(Debug)]
102-
pub(crate) struct VirtioQueueUsedHandler {
103-
core: QueueCoreCompleteWork,
104-
notify_guest: Interrupt,
105-
}
106-
107-
impl VirtioQueueUsedHandler {
108-
pub(crate) fn new(core: QueueCoreCompleteWork, notify_guest: Interrupt) -> Self {
109-
Self { core, notify_guest }
110-
}
111-
112-
pub(crate) fn complete_descriptor(&mut self, work: &QueueWork, bytes_written: u32) {
113-
match self.core.complete_descriptor(work, bytes_written) {
114-
Ok(true) => {
115-
self.notify_guest.deliver();
116-
}
117-
Ok(false) => {}
118-
Err(err) => {
119-
tracelimit::error_ratelimited!(
120-
error = &err as &dyn std::error::Error,
121-
"failed to complete descriptor"
122-
);
123-
}
124-
}
125-
}
126-
}
127-
128100
/// A descriptor chain popped from a [`VirtioQueue`].
129101
///
130-
/// The device must call [`complete`](Self::complete) exactly once to post a
102+
/// The device must call [`VirtioQueue::complete`] exactly once to post a
131103
/// completion to the guest's used ring. Dropping without completing is a bug
132104
/// and will not automatically post a completion.
133105
#[must_use]
134106
pub struct VirtioQueueCallbackWork {
135-
used_queue_handler: Arc<Mutex<VirtioQueueUsedHandler>>,
136107
work: QueueWork,
137108
pub payload: Vec<VirtioQueuePayload>,
138-
completed: bool,
139109
}
140110

141111
impl VirtioQueueCallbackWork {
142-
pub(crate) fn new(
143-
mut work: QueueWork,
144-
used_queue_handler: &Arc<Mutex<VirtioQueueUsedHandler>>,
145-
) -> Self {
146-
let used_queue_handler = used_queue_handler.clone();
112+
pub(crate) fn new(mut work: QueueWork) -> Self {
147113
let payload = std::mem::take(&mut work.payload);
148-
Self {
149-
work,
150-
payload,
151-
used_queue_handler,
152-
completed: false,
153-
}
154-
}
155-
156-
pub fn complete(&mut self, bytes_written: u32) {
157-
assert!(!self.completed);
158-
self.used_queue_handler
159-
.lock()
160-
.complete_descriptor(&self.work, bytes_written);
161-
self.completed = true;
114+
Self { work, payload }
162115
}
163116

164117
pub fn descriptor_index(&self) -> u16 {
@@ -290,10 +243,10 @@ impl<'a> PeekedWork<'a> {
290243
/// Consume this peeked work, advancing the queue's available index.
291244
///
292245
/// Returns a [`VirtioQueueCallbackWork`] that must be explicitly
293-
/// completed via [`VirtioQueueCallbackWork::complete`].
246+
/// completed via [`VirtioQueue::complete`].
294247
pub fn consume(self) -> VirtioQueueCallbackWork {
295248
self.queue.core.advance(&self.work);
296-
VirtioQueueCallbackWork::new(self.work, &self.queue.used_handler)
249+
VirtioQueueCallbackWork::new(self.work)
297250
}
298251
}
299252

@@ -302,7 +255,9 @@ pub struct VirtioQueue {
302255
#[inspect(flatten)]
303256
core: QueueCoreGetWork,
304257
#[inspect(skip)]
305-
used_handler: Arc<Mutex<VirtioQueueUsedHandler>>,
258+
complete: QueueCoreCompleteWork,
259+
#[inspect(skip)]
260+
notify_guest: Interrupt,
306261
#[inspect(skip)]
307262
queue_event: PolledWait<Event>,
308263
}
@@ -317,13 +272,10 @@ impl VirtioQueue {
317272
initial_state: Option<QueueState>,
318273
) -> Result<Self, QueueError> {
319274
let (get_work, complete_work) = new_queue(features, mem, params, initial_state)?;
320-
let used_handler = Arc::new(Mutex::new(VirtioQueueUsedHandler::new(
321-
complete_work,
322-
notify,
323-
)));
324275
Ok(Self {
325276
core: get_work,
326-
used_handler,
277+
complete: complete_work,
278+
notify_guest: notify,
327279
queue_event,
328280
})
329281
}
@@ -332,7 +284,7 @@ impl VirtioQueue {
332284
pub fn queue_state(&self) -> QueueState {
333285
QueueState {
334286
avail_index: self.core.avail_index(),
335-
used_index: self.used_handler.lock().core.used_index(),
287+
used_index: self.complete.used_index(),
336288
}
337289
}
338290

@@ -362,7 +314,7 @@ impl VirtioQueue {
362314
.core
363315
.try_next_work()
364316
.map_err(Error::other)?
365-
.map(|work| VirtioQueueCallbackWork::new(work, &self.used_handler)))
317+
.map(VirtioQueueCallbackWork::new))
366318
}
367319

368320
/// Peek at the next available descriptor without advancing the available
@@ -402,6 +354,28 @@ impl VirtioQueue {
402354
Ok(PeekedWork::new(self, work))
403355
}
404356

357+
/// Complete a descriptor previously obtained from this queue.
358+
///
359+
/// Writes `bytes_written` to the used ring and delivers an interrupt
360+
/// to the guest (unless interrupt suppression is active).
361+
///
362+
/// Takes ownership of the work item, ensuring it can only be completed
363+
/// once.
364+
pub fn complete(&mut self, work: VirtioQueueCallbackWork, bytes_written: u32) {
365+
match self.complete.complete_descriptor(&work.work, bytes_written) {
366+
Ok(true) => {
367+
self.notify_guest.deliver();
368+
}
369+
Ok(false) => {}
370+
Err(err) => {
371+
tracelimit::error_ratelimited!(
372+
error = &err as &dyn std::error::Error,
373+
"failed to complete descriptor"
374+
);
375+
}
376+
}
377+
}
378+
405379
fn poll_next_buffer(
406380
&mut self,
407381
cx: &mut Context<'_>,
@@ -415,14 +389,6 @@ impl VirtioQueue {
415389
}
416390
}
417391

418-
impl Drop for VirtioQueue {
419-
fn drop(&mut self) {
420-
if Arc::get_mut(&mut self.used_handler).is_none() {
421-
tracing::error!("Virtio queue dropped with outstanding work pending")
422-
}
423-
}
424-
}
425-
426392
impl Stream for VirtioQueue {
427393
type Item = Result<VirtioQueueCallbackWork, Error>;
428394

0 commit comments

Comments
 (0)