turnloop/buffer.rs
1//! Stable host memory and explicitly released pool leases.
2use std::{cell::RefCell, ptr::NonNull, rc::Rc};
3
4/// A stable, initialized, immutable byte region. Does not own the memory.
5#[derive(Debug)]
6pub struct IoBuf {
7 ptr: NonNull<u8>,
8 len: usize,
9}
10impl IoBuf {
11 /// # Safety
12 /// `ptr` must be non-null and readable for `len` bytes. Keep the region alive,
13 /// unmoved and unmodified until the terminal completion is delivered, or until
14 /// the driver is dropped (which must quiesce all native I/O before returning).
15 pub unsafe fn from_raw_parts(ptr: *const u8, len: usize) -> Self {
16 assert!(
17 len <= isize::MAX as usize,
18 "buffer length fits a Rust slice"
19 );
20 Self {
21 ptr: NonNull::new(ptr.cast_mut()).expect("non-null buffer pointer"),
22 len,
23 }
24 }
25 /// Return the number of bytes in this region.
26 pub fn len(&self) -> usize {
27 self.len
28 }
29 /// Whether this region contains zero bytes.
30 pub fn is_empty(&self) -> bool {
31 self.len == 0
32 }
33 /// Return the stable address of the readable byte region.
34 pub fn as_ptr(&self) -> *const u8 {
35 self.ptr.as_ptr()
36 }
37}
38/// A stable, exclusively writable region. Does not own the memory.
39#[derive(Debug)]
40pub struct IoBufMut {
41 ptr: NonNull<u8>,
42 len: usize,
43}
44impl IoBufMut {
45 /// # Safety
46 /// `ptr` must be non-null and writable for `len` bytes. The region must remain
47 /// alive and unmoved, and no other access is permitted, until delivery of the
48 /// terminal completion or return from driver destruction.
49 pub unsafe fn from_raw_parts(ptr: *mut u8, len: usize) -> Self {
50 assert!(
51 len <= isize::MAX as usize,
52 "buffer length fits a Rust slice"
53 );
54 Self {
55 ptr: NonNull::new(ptr).expect("non-null buffer pointer"),
56 len,
57 }
58 }
59 /// Return the number of bytes in this region.
60 pub fn len(&self) -> usize {
61 self.len
62 }
63 /// Whether this region contains zero bytes.
64 pub fn is_empty(&self) -> bool {
65 self.len == 0
66 }
67 /// Return the stable address of the exclusively writable byte region.
68 pub fn as_mut_ptr(&self) -> *mut u8 {
69 self.ptr.as_ptr()
70 }
71}
72#[derive(Debug)]
73/// Choose host-provided memory or an explicitly released pool lease for a read.
74pub enum ReadBuf {
75 /// Host-provided stable memory, retained under its constructor lifetime contract.
76 Provided(IoBufMut),
77 /// Use a reusable pool buffer; exhaustion delays progress until a lease is released.
78 Pooled,
79}
80#[derive(Debug)]
81/// Stable write data, either borrowed under an unsafe lifetime contract or owned.
82pub enum WriteBuf {
83 /// Host-provided stable memory, retained under its constructor lifetime contract.
84 Provided(IoBuf),
85 /// Owned initialized bytes, retained until the operation terminates.
86 Owned(Vec<u8>),
87}
88impl WriteBuf {
89 /// Borrow the initialized bytes covered by this buffer.
90 pub fn as_slice(&self) -> &[u8] {
91 match self {
92 Self::Provided(b) => {
93 // SAFETY: construction promises a readable region for the entire op lifetime.
94 unsafe { std::slice::from_raw_parts(b.as_ptr(), b.len()) }
95 }
96 Self::Owned(b) => b,
97 }
98 }
99}
100
101/// Fixed upper bound keeps writev descriptors inline in the operation table.
102pub const MAX_IOV: usize = 8;
103#[derive(Debug)]
104/// Up to MAX_IOV owned or provided segments stored inline.
105pub struct WriteVectored {
106 /// Inline segments in transmission order; unused entries are None.
107 pub bufs: [Option<WriteBuf>; MAX_IOV],
108}
109impl WriteVectored {
110 /// Build inline vectored storage; returns InvalidInput if more than MAX_IOV segments are supplied.
111 pub fn new(bufs: impl IntoIterator<Item = WriteBuf>) -> crate::Result<Self> {
112 let mut out = Self {
113 bufs: std::array::from_fn(|_| None),
114 };
115 for (i, b) in bufs.into_iter().enumerate() {
116 if i == MAX_IOV {
117 return Err(crate::Error::new(crate::ErrorKind::InvalidInput));
118 }
119 out.bufs[i] = Some(b);
120 }
121 Ok(out)
122 }
123}
124
125#[derive(Clone, Debug)]
126/// Fixed reusable read storage; leases return buffers when explicitly released or dropped.
127pub struct BufferPool {
128 inner: Rc<RefCell<Vec<Vec<u8>>>>,
129}
130impl BufferPool {
131 /// Allocate the fixed number and size of reusable read buffers.
132 pub fn new(count: usize, size: usize) -> Self {
133 Self {
134 inner: Rc::new(RefCell::new((0..count).map(|_| vec![0; size]).collect())),
135 }
136 }
137 #[cfg(any(
138 turnloop_backend = "kqueue",
139 turnloop_backend = "epoll",
140 turnloop_backend = "iocp",
141 turnloop_backend = "wasi_p2",
142 all(turnloop_backend = "wasi_p3", feature = "wasi-p3-experimental")
143 ))]
144 pub(crate) fn available(&self) -> bool {
145 !self.inner.borrow().is_empty()
146 }
147 /// Exhaustion applies backpressure: the backend keeps the read pending.
148 pub fn acquire(&self) -> Option<BufLease> {
149 self.inner.borrow_mut().pop().map(|data| BufLease {
150 data: Some(data),
151 len: 0,
152 pool: self.inner.clone(),
153 })
154 }
155}
156/// Owns the buffer until explicitly dropped/released. May safely survive turns.
157/// Keeping all leases applies backpressure; it never allocates a new pool buffer.
158#[derive(Debug)]
159pub struct BufLease {
160 data: Option<Vec<u8>>,
161 len: usize,
162 pool: Rc<RefCell<Vec<Vec<u8>>>>,
163}
164impl BufLease {
165 /// Borrow the initialized bytes covered by this buffer.
166 pub fn as_slice(&self) -> &[u8] {
167 &self.data.as_ref().expect("live lease")[..self.len]
168 }
169 /// Borrow the full exclusive pool-buffer storage for a backend read.
170 pub fn writable(&mut self) -> &mut [u8] {
171 self.data.as_mut().expect("live lease")
172 }
173 /// Set the initialized completion length; panics if it exceeds buffer capacity.
174 pub fn set_len(&mut self, len: usize) {
175 assert!(len <= self.data.as_ref().expect("live lease").len());
176 self.len = len;
177 }
178 /// Return this lease to its originating pool.
179 pub fn release(self) {}
180}
181impl Drop for BufLease {
182 fn drop(&mut self) {
183 if let Some(data) = self.data.take() {
184 self.pool.borrow_mut().push(data);
185 }
186 }
187}