block: Add a SyncCompletionQueue for the sync engines

The raw, qcow, and vhdx sync engines each carried an EventFd and a
VecDeque of completions and repeated the same push and signal idiom at
every completion site. Bundle the two into a SyncCompletionQueue with
complete, notifier, and next_completed, and route the three engines
through it. The repeated eventfd signal now lives in one place.

Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
Anatol Belski
2026-07-21 07:58:02 +02:00
committed by Rob Bradford
parent 9d3451a56a
commit 331b2bba53
5 changed files with 82 additions and 62 deletions

View File

@@ -5,7 +5,6 @@
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::cmp::min;
use std::collections::VecDeque;
use std::os::unix::fs::FileExt;
use std::sync::Arc;
@@ -18,7 +17,9 @@ use super::metadata::{
BackingRead, ClusterReadMapping, ClusterWriteMapping, DeallocAction, QcowMetadata,
};
use super::qcow_raw_file::QcowRawFile;
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
use crate::async_io::{
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, SyncCompletionQueue,
};
pub(super) struct QcowSync {
metadata: Arc<QcowMetadata>,
@@ -28,8 +29,7 @@ pub(super) struct QcowSync {
sparse: bool,
cluster_size: u64,
decoder: Arc<dyn Decoder>,
eventfd: EventFd,
completion_list: VecDeque<AsyncIoCompletion>,
completions: SyncCompletionQueue,
}
impl QcowSync {
@@ -46,9 +46,7 @@ impl QcowSync {
data_file,
backing_file,
sparse,
eventfd: EventFd::new(libc::EFD_NONBLOCK)
.expect("Failed creating EventFd for QcowSync"),
completion_list: VecDeque::new(),
completions: SyncCompletionQueue::new(),
}
}
@@ -200,7 +198,7 @@ impl QcowSync {
impl AsyncIo for QcowSync {
fn notifier(&self) -> &EventFd {
&self.eventfd
self.completions.notifier()
}
fn submit_data_operation(&mut self, mut op: AsyncIoOperation) -> AsyncIoResult<()> {
@@ -210,24 +208,22 @@ impl AsyncIo for QcowSync {
} else {
self.write_operation(&op)?
};
self.completion_list
.push_back(AsyncIoCompletion::from_operation(op, total_len as i32));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::from_operation(op, total_len as i32));
Ok(())
}
fn fsync(&mut self, user_data: Option<u64>) -> AsyncIoResult<()> {
self.metadata.flush().map_err(AsyncIoError::Fsync)?;
if let Some(user_data) = user_data {
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, 0, None));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::new(user_data, 0, None));
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
self.completion_list.pop_front()
self.completions.next_completed()
}
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
@@ -247,9 +243,8 @@ impl AsyncIo for QcowSync {
for action in &actions {
self.apply_dealloc_action(action);
}
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, 0, None));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::new(user_data, 0, None));
Ok(())
}
Err(e) => {
@@ -258,9 +253,8 @@ impl AsyncIo for QcowSync {
} else {
-libc::EIO
};
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, errno, None));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::new(user_data, errno, None));
Ok(())
}
}
@@ -283,9 +277,8 @@ impl AsyncIo for QcowSync {
for action in &actions {
self.apply_dealloc_action(action);
}
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, 0, None));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::new(user_data, 0, None));
Ok(())
}
Err(e) => {
@@ -294,9 +287,8 @@ impl AsyncIo for QcowSync {
} else {
-libc::EIO
};
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, errno, None));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::new(user_data, errno, None));
Ok(())
}
}

View File

@@ -4,20 +4,20 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::collections::VecDeque;
use std::io;
use std::os::unix::io::AsRawFd;
use vmm_sys_util::eventfd::EventFd;
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
use crate::async_io::{
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, SyncCompletionQueue,
};
use crate::sparse::{punch_hole, write_zeroes};
use crate::{AlignedFile, is_block_device};
pub(crate) struct RawSync {
raw_file: AlignedFile,
eventfd: EventFd,
completion_list: VecDeque<AsyncIoCompletion>,
completions: SyncCompletionQueue,
alignment: u64,
is_block_device: bool,
}
@@ -28,8 +28,7 @@ impl RawSync {
let alignment = raw_file.alignment() as u64;
RawSync {
raw_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK).expect("Failed creating EventFd for RawFile"),
completion_list: VecDeque::new(),
completions: SyncCompletionQueue::new(),
alignment,
is_block_device,
}
@@ -38,7 +37,7 @@ impl RawSync {
impl AsyncIo for RawSync {
fn notifier(&self) -> &EventFd {
&self.eventfd
self.completions.notifier()
}
fn alignment(&self) -> u64 {
@@ -61,9 +60,8 @@ impl AsyncIo for RawSync {
unsafe { self.raw_file.write_vectored_at(iovecs, offset) }
.map_err(AsyncIoError::WriteVectored)?
} as i32;
self.completion_list
.push_back(AsyncIoCompletion::from_operation(op, result));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::from_operation(op, result));
Ok(())
}
@@ -76,33 +74,30 @@ impl AsyncIo for RawSync {
}
if let Some(user_data) = user_data {
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, result, None));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::new(user_data, result, None));
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
self.completion_list.pop_front()
self.completions.next_completed()
}
fn punch_hole(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
punch_hole(&mut self.raw_file, self.is_block_device, offset, length)
.map_err(AsyncIoError::PunchHole)?;
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, 0, None));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::new(user_data, 0, None));
Ok(())
}
fn write_zeroes(&mut self, offset: u64, length: u64, user_data: u64) -> AsyncIoResult<()> {
write_zeroes(&mut self.raw_file, self.is_block_device, offset, length)
.map_err(AsyncIoError::WriteZeroes)?;
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, 0, None));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::new(user_data, 0, None));
Ok(())
}
}

View File

@@ -4,19 +4,19 @@
//
// SPDX-License-Identifier: Apache-2.0
use std::collections::VecDeque;
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::sync::{Arc, Mutex};
use vmm_sys_util::eventfd::EventFd;
use crate::async_io::{AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult};
use crate::async_io::{
AsyncIo, AsyncIoCompletion, AsyncIoError, AsyncIoOperation, AsyncIoResult, SyncCompletionQueue,
};
use crate::formats::vhdx::Vhdx;
pub(super) struct VhdxSync {
vhdx_file: Arc<Mutex<Vhdx>>,
eventfd: EventFd,
completion_list: VecDeque<AsyncIoCompletion>,
completions: SyncCompletionQueue,
size: u64,
}
@@ -24,9 +24,7 @@ impl VhdxSync {
pub(super) fn new(vhdx_file: Arc<Mutex<Vhdx>>, size: u64) -> Self {
VhdxSync {
vhdx_file,
eventfd: EventFd::new(libc::EFD_NONBLOCK)
.expect("Failed creating EventFd for VhdxSync"),
completion_list: VecDeque::new(),
completions: SyncCompletionQueue::new(),
size,
}
}
@@ -61,7 +59,7 @@ impl VhdxSync {
impl AsyncIo for VhdxSync {
fn notifier(&self) -> &EventFd {
&self.eventfd
self.completions.notifier()
}
fn submit_data_operation(&mut self, op: AsyncIoOperation) -> AsyncIoResult<()> {
@@ -74,9 +72,8 @@ impl AsyncIo for VhdxSync {
self.write_operation(&op)?
};
self.completion_list
.push_back(AsyncIoCompletion::from_operation(op, result as i32));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::from_operation(op, result as i32));
Ok(())
}
@@ -87,15 +84,14 @@ impl AsyncIo for VhdxSync {
.flush()
.map_err(AsyncIoError::Fsync)?;
if let Some(user_data) = user_data {
self.completion_list
.push_back(AsyncIoCompletion::new(user_data, 0, None));
self.eventfd.write(1).unwrap();
self.completions
.complete(AsyncIoCompletion::new(user_data, 0, None));
}
Ok(())
}
fn next_completed_request(&mut self) -> Option<AsyncIoCompletion> {
self.completion_list.pop_front()
self.completions.next_completed()
}
fn punch_hole(&mut self, _offset: u64, _length: u64, _user_data: u64) -> AsyncIoResult<()> {

View File

@@ -17,6 +17,7 @@ use std::{io, result};
pub use aio_data_io::AioDataIo;
pub use completion::AsyncIoCompletion;
pub(crate) use completion::SyncCompletionQueue;
pub use guest_memory_target::GuestMemoryTarget;
pub use operation::AsyncIoOperation;
pub use owned_io_buffer::OwnedIoBuffer;

View File

@@ -2,6 +2,10 @@
//
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
use std::collections::VecDeque;
use vmm_sys_util::eventfd::EventFd;
use super::{AsyncIoOperation, OwnedIoBuffer};
/// Completion returned by an owned async I/O backend.
@@ -40,3 +44,35 @@ impl AsyncIoCompletion {
Self::new(user_data, result, op.into_completion_buffer())
}
}
/// Pending completions plus the eventfd that signals the device to
/// drain them. Sync engines run each operation inline, enqueue its
/// completion, and signal the eventfd.
pub(crate) struct SyncCompletionQueue {
queue: VecDeque<AsyncIoCompletion>,
eventfd: EventFd,
}
impl SyncCompletionQueue {
pub(crate) fn new() -> Self {
Self {
queue: VecDeque::new(),
eventfd: EventFd::new(libc::EFD_NONBLOCK)
.expect("Failed creating EventFd for sync completion queue"),
}
}
pub(crate) fn notifier(&self) -> &EventFd {
&self.eventfd
}
/// Enqueues a completion and signals the eventfd.
pub(crate) fn complete(&mut self, completion: AsyncIoCompletion) {
self.queue.push_back(completion);
self.eventfd.write(1).unwrap();
}
pub(crate) fn next_completed(&mut self) -> Option<AsyncIoCompletion> {
self.queue.pop_front()
}
}