Skip to content

Commit bb5a00c

Browse files
authored
FixedBufPool: a dynamic fixed buffer collection (#190)
Make the internal infrastructure for fixed buffers more flexible, allowing `FixedBuf` handles to be backed by alternative collections. Add one new collection as `FixedBufPool`, enabling dynamic retrieval of free buffers.
1 parent 026d57a commit bb5a00c

File tree

8 files changed

+630
-150
lines changed

8 files changed

+630
-150
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ futures = "0.3.25"
3232
criterion = "0.4.0"
3333
# we use joinset in our tests
3434
tokio = "1.21.0"
35+
nix = "0.26.1"
3536

3637
[package.metadata.docs.rs]
3738
all-features = true

src/buf/fixed/buffers.rs

Lines changed: 19 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,22 @@
1-
use libc::{iovec, UIO_MAXIOV};
2-
use std::cmp;
3-
use std::mem;
4-
use std::ptr;
5-
use std::slice;
1+
use libc::iovec;
62

7-
// Internal state shared by FixedBufRegistry and FixedBuf handles.
8-
pub(crate) struct FixedBuffers {
9-
// Pointer to an allocated array of iovec records referencing
10-
// the allocated buffers. The number of initialized records is the
11-
// same as the length of the states array.
12-
raw_bufs: ptr::NonNull<iovec>,
13-
// State information on the buffers. Indices in this array correspond to
14-
// the indices in the array at raw_bufs.
15-
states: Vec<BufState>,
16-
// Original capacity of raw_bufs as a Vec.
17-
orig_cap: usize,
18-
}
19-
20-
// State information of a buffer in the registry,
21-
enum BufState {
22-
// The buffer is not in use.
23-
// The field records the length of the initialized part.
24-
Free { init_len: usize },
25-
// The buffer is checked out.
26-
// Its data are logically owned by the FixedBuf handle,
27-
// which also keeps track of the length of the initialized part.
28-
CheckedOut,
29-
}
30-
31-
impl FixedBuffers {
32-
pub(crate) fn new(bufs: impl Iterator<Item = Vec<u8>>) -> Self {
33-
let bufs = bufs.take(cmp::min(UIO_MAXIOV as usize, 65_536));
34-
let (size_hint, _) = bufs.size_hint();
35-
let mut iovecs = Vec::with_capacity(size_hint);
36-
let mut states = Vec::with_capacity(size_hint);
37-
for mut buf in bufs {
38-
iovecs.push(iovec {
39-
iov_base: buf.as_mut_ptr() as *mut _,
40-
iov_len: buf.capacity(),
41-
});
42-
states.push(BufState::Free {
43-
init_len: buf.len(),
44-
});
45-
mem::forget(buf);
46-
}
47-
debug_assert_eq!(iovecs.len(), states.len());
48-
// Safety: Vec::as_mut_ptr never returns null
49-
let raw_bufs = unsafe { ptr::NonNull::new_unchecked(iovecs.as_mut_ptr()) };
50-
let orig_cap = iovecs.capacity();
51-
mem::forget(iovecs);
52-
FixedBuffers {
53-
raw_bufs,
54-
states,
55-
orig_cap,
56-
}
57-
}
58-
59-
// If the indexed buffer is free, changes its state to checked out and
60-
// returns its data. If the buffer is already checked out, returns None.
61-
pub(crate) fn check_out(&mut self, index: usize) -> Option<(iovec, usize)> {
62-
let iovecs_ptr = self.raw_bufs;
63-
self.states.get_mut(index).and_then(|state| match *state {
64-
BufState::Free { init_len } => {
65-
*state = BufState::CheckedOut;
66-
// Safety: the allocated array under the pointer is valid
67-
// for the lifetime of self, the index is inside the array
68-
// as checked by Vec::get_mut above, called on the array of
69-
// states that has the same length.
70-
let iovec = unsafe { iovecs_ptr.as_ptr().add(index).read() };
71-
Some((iovec, init_len))
72-
}
73-
BufState::CheckedOut => None,
74-
})
75-
}
76-
77-
// Sets the indexed buffer's state to free and records the updated length
78-
// of its initialized part. The buffer addressed must be in the checked out
79-
// state, otherwise this function may panic.
80-
pub(crate) fn check_in(&mut self, index: usize, init_len: usize) {
81-
let state = self.states.get_mut(index).expect("invalid buffer index");
82-
debug_assert!(
83-
matches!(state, BufState::CheckedOut),
84-
"the buffer must be checked out"
85-
);
86-
*state = BufState::Free { init_len };
87-
}
88-
89-
pub(crate) fn iovecs(&self) -> &[iovec] {
90-
// Safety: the raw_bufs pointer is valid for the lifetime of self,
91-
// the slice length is valid by construction.
92-
unsafe { slice::from_raw_parts(self.raw_bufs.as_ptr(), self.states.len()) }
93-
}
94-
}
3+
// Abstracts management of fixed buffers in a buffer registry.
4+
pub(crate) trait FixedBuffers {
5+
// Provides access to the raw buffers as a slice of iovec.
6+
fn iovecs(&self) -> &[iovec];
957

96-
impl Drop for FixedBuffers {
97-
fn drop(&mut self) {
98-
let iovecs = unsafe {
99-
Vec::from_raw_parts(self.raw_bufs.as_ptr(), self.states.len(), self.orig_cap)
100-
};
101-
for (i, iovec) in iovecs.iter().enumerate() {
102-
match self.states[i] {
103-
BufState::Free { init_len } => {
104-
let ptr = iovec.iov_base as *mut u8;
105-
let cap = iovec.iov_len;
106-
let v = unsafe { Vec::from_raw_parts(ptr, init_len, cap) };
107-
mem::drop(v);
108-
}
109-
BufState::CheckedOut => unreachable!("all buffers must be checked in"),
110-
}
111-
}
112-
}
8+
/// Sets the indexed buffer's state to free and records the updated length
9+
/// of its initialized part.
10+
///
11+
/// # Panics
12+
///
13+
/// The buffer addressed must be in the checked out state,
14+
/// otherwise this function may panic.
15+
///
16+
/// # Safety
17+
///
18+
/// While the implementation of this method typically does not need to
19+
/// do anything unsafe, the caller must ensure that the bytes in the buffer
20+
/// are initialized up to the specified length.
21+
unsafe fn check_in(&mut self, buf_index: u16, init_len: usize);
11322
}

src/buf/fixed/handle.rs

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,59 @@ use std::mem::ManuallyDrop;
88
use std::ops::{Deref, DerefMut};
99
use std::rc::Rc;
1010

11+
// Data to construct a `FixedBuf` handle from.
12+
pub(super) struct CheckedOutBuf {
13+
// Pointer and size of the buffer.
14+
pub iovec: iovec,
15+
// Length of the initialized part.
16+
pub init_len: usize,
17+
// Buffer index.
18+
pub index: u16,
19+
}
20+
1121
/// A unique handle to a memory buffer that can be pre-registered with
1222
/// the kernel for `io-uring` operations.
1323
///
14-
/// `FixedBuf` handles can be obtained from a [`FixedBufRegistry`] collection.
24+
/// `FixedBuf` handles can be obtained from a collection of fixed buffers,
25+
/// either [`FixedBufRegistry`] or [`FixedBufPool`].
1526
/// For each buffer, only a single `FixedBuf` handle can be either used by the
1627
/// application code or owned by an I/O operation at any given time,
1728
/// thus avoiding data races between `io-uring` operations in flight and
1829
/// the application accessing buffer data.
1930
///
2031
/// [`FixedBufRegistry`]: super::FixedBufRegistry
32+
/// [`FixedBufPool`]: super::FixedBufPool
2133
///
2234
pub struct FixedBuf {
23-
registry: Rc<RefCell<FixedBuffers>>,
35+
registry: Rc<RefCell<dyn FixedBuffers>>,
2436
buf: ManuallyDrop<Vec<u8>>,
2537
index: u16,
2638
}
2739

2840
impl Drop for FixedBuf {
2941
fn drop(&mut self) {
3042
let mut registry = self.registry.borrow_mut();
31-
debug_assert_eq!(
32-
registry.iovecs()[self.index as usize].iov_base as *const u8,
33-
self.buf.as_ptr()
34-
);
35-
debug_assert_eq!(
36-
registry.iovecs()[self.index as usize].iov_len,
37-
self.buf.capacity()
38-
);
39-
registry.check_in(self.index as usize, self.buf.len());
43+
// Safety: the length of the initialized data in the buffer has been
44+
// maintained accordingly to the safety contracts on
45+
// Self::new and IoBufMut.
46+
unsafe {
47+
registry.check_in(self.index, self.buf.len());
48+
}
4049
}
4150
}
4251

4352
impl FixedBuf {
44-
pub(super) unsafe fn new(
45-
registry: Rc<RefCell<FixedBuffers>>,
46-
iovec: iovec,
47-
init_len: usize,
48-
index: u16,
49-
) -> Self {
53+
// Safety: Validity constraints must apply to CheckedOutBuf members:
54+
// - iovec must refer to an array allocated by Vec<u8>;
55+
// - the array will not be deallocated until the buffer is checked in;
56+
// - the data in the array must be initialized up to the number of bytes
57+
// given in init_len.
58+
pub(super) unsafe fn new(registry: Rc<RefCell<dyn FixedBuffers>>, data: CheckedOutBuf) -> Self {
59+
let CheckedOutBuf {
60+
iovec,
61+
init_len,
62+
index,
63+
} = data;
5064
let buf = Vec::from_raw_parts(iovec.iov_base as _, init_len, iovec.iov_len);
5165
FixedBuf {
5266
registry,

src/buf/fixed/mod.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,25 @@
44
//! the `tokio-uring` runtime. Operations like [`File::read_fixed_at`][rfa] and
55
//! [`File::write_fixed_at`][wfa] make use of buffers pre-mapped by
66
//! the kernel to reduce per-I/O overhead.
7-
//! The [`FixedBufRegistry::register`] method is used to register a collection of
8-
//! buffers with the kernel; it must be called before any of the [`FixedBuf`]
9-
//! handles to the collection's buffers can be used with I/O operations.
7+
//!
8+
//! Two kinds of buffer collections are provided: [`FixedBufRegistry`] and
9+
//! [`FixedBufPool`], realizing two different patterns of buffer management.
10+
//! The `register` method on either of these types is used to register a
11+
//! collection of buffers with the kernel. It must be called before any of
12+
//! the [`FixedBuf`] handles to the collection's buffers can be used with
13+
//! I/O operations.
1014
//!
1115
//! [rfa]: crate::fs::File::read_fixed_at
1216
//! [wfa]: crate::fs::File::write_fixed_at
1317
14-
mod buffers;
15-
pub(crate) use self::buffers::FixedBuffers;
16-
1718
mod handle;
1819
pub use handle::FixedBuf;
1920

21+
mod buffers;
22+
pub(crate) use buffers::FixedBuffers;
23+
24+
mod pool;
25+
pub use pool::FixedBufPool;
26+
2027
mod registry;
2128
pub use registry::FixedBufRegistry;

0 commit comments

Comments
 (0)