Skip to main content

tpt_torus_core/
lib.rs

1//! TPT Torus Core — the Virtual Torus abstraction.
2//!
3//! Exposes the [`Torus`] handle, and the [`Flow`] (submission) / [`Result`] (completion)
4//! types that replace raw SQE/CQE across all backends.
5
6pub mod async_api;
7#[cfg(feature = "tokio")]
8pub mod async_tokio;
9pub mod backend;
10pub mod cgroup;
11pub mod error;
12pub mod flow;
13pub mod lease;
14pub mod observability;
15pub mod operation;
16pub mod raw_api;
17pub mod result;
18pub mod rings;
19pub mod torus_panic;
20
21pub use error::{Error, Result};
22pub use flow::Flow;
23pub use lease::{LeaseError, LeaseRegistry, SharedLeaseRegistry};
24pub use operation::{IoSlice, Operation};
25pub use result::Result as TorusResult;
26pub use rings::{CompletionRing, SubmissionRing};
27pub use torus_panic::TorusPanic;
28
29use backend::Backend;
30use std::sync::atomic::{AtomicU32, Ordering};
31use std::sync::{Arc, Mutex};
32
33/// The main context object for the Virtual Torus.
34///
35/// `Torus` owns the virtual submission and completion rings and delegates
36/// to a platform-specific [`Backend`] for actual I/O. It is thread-safe
37/// and can be shared across threads via `Arc<Torus>`.
38pub struct Torus {
39    sq: SubmissionRing,
40    cq: CompletionRing,
41    backend: Mutex<Box<dyn Backend>>,
42    /// In-flight operation spans, keyed by `user_data`, consumed on `reap`.
43    /// Only present when the `tracing` feature is enabled.
44    #[cfg(feature = "tracing")]
45    spans: Mutex<std::collections::HashMap<u64, crate::observability::FlowSpan>>,
46}
47
48// SAFETY: Torus is thread-safe. The backend is behind a Mutex, and the rings
49// use atomic operations for synchronization.
50unsafe impl Send for Torus {}
51unsafe impl Sync for Torus {}
52
53impl Torus {
54    /// Create a new Torus instance with the given ring size and backend.
55    ///
56    /// `ring_entries` must be a power of two (e.g. 256, 1024, 4096).
57    pub fn new(ring_entries: u32, backend: Box<dyn Backend>) -> Result<Self> {
58        if !ring_entries.is_power_of_two() {
59            return Err(Error::InvalidParam("ring_entries must be a power of two"));
60        }
61        Ok(Self {
62            sq: SubmissionRing::new(ring_entries),
63            cq: CompletionRing::new(ring_entries),
64            backend: Mutex::new(backend),
65            #[cfg(feature = "tracing")]
66            spans: Mutex::new(std::collections::HashMap::new()),
67        })
68    }
69
70    /// Submit a single flow to the Virtual Torus.
71    pub fn submit(&self, flow: &Flow) -> Result<()> {
72        let n = self
73            .backend
74            .lock()
75            .unwrap()
76            .submit(std::slice::from_ref(flow))?;
77        if n == 0 {
78            return Err(Error::SubmissionFull);
79        }
80        #[cfg(feature = "tracing")]
81        {
82            self.spans
83                .lock()
84                .unwrap()
85                .insert(flow.user_data(), crate::observability::FlowSpan::new(flow));
86        }
87        Ok(())
88    }
89
90    /// Submit a batch of flows to the Virtual Torus.
91    pub fn submit_batch(&self, flows: &[Flow]) -> Result<usize> {
92        let n = self.backend.lock().unwrap().submit(flows)?;
93        #[cfg(feature = "tracing")]
94        {
95            let mut spans = self.spans.lock().unwrap();
96            for flow in flows.iter().take(n) {
97                spans.insert(flow.user_data(), crate::observability::FlowSpan::new(flow));
98            }
99        }
100        Ok(n)
101    }
102
103    /// Submit a vectored read (readv) operation.
104    ///
105    /// Reads from `fd` at `offset` into multiple buffers described by `bufs`.
106    /// Returns the total number of bytes read across all buffers.
107    pub fn readv(&self, fd: i32, bufs: &[IoSlice], offset: u64, user_data: u64) -> Result<()> {
108        let flow = Flow::with_user_data(
109            Operation::Readv {
110                fd,
111                bufs: bufs.as_ptr(),
112                buf_count: bufs.len() as u32,
113                offset,
114            },
115            user_data,
116        );
117        self.submit(&flow)
118    }
119
120    /// Submit a vectored write (writev) operation.
121    ///
122    /// Writes to `fd` at `offset` from multiple buffers described by `bufs`.
123    /// Returns the total number of bytes written across all buffers.
124    pub fn writev(&self, fd: i32, bufs: &[IoSlice], offset: u64, user_data: u64) -> Result<()> {
125        let flow = Flow::with_user_data(
126            Operation::Writev {
127                fd,
128                bufs: bufs.as_ptr(),
129                buf_count: bufs.len() as u32,
130                offset,
131            },
132            user_data,
133        );
134        self.submit(&flow)
135    }
136
137    /// Reap all available completions.
138    pub fn reap(&self, results: &mut Vec<TorusResult>) -> Result<usize> {
139        let count = self.backend.lock().unwrap().reap(results)?;
140        #[cfg(feature = "tracing")]
141        {
142            let mut spans = self.spans.lock().unwrap();
143            for r in results.iter() {
144                if let Some(span) = spans.remove(&r.user_data) {
145                    span.complete(r.result);
146                }
147            }
148        }
149        Ok(count)
150    }
151
152    /// Block until at least one completion is available.
153    pub fn wait(&self, timeout_us: u64) -> Result<()> {
154        #[cfg(feature = "tracing")]
155        let _span = tracing::debug_span!("torus_wait", timeout_us = timeout_us).entered();
156        self.backend.lock().unwrap().wait(timeout_us)
157    }
158
159    /// Number of in-flight operations.
160    pub fn in_flight(&self) -> u32 {
161        self.backend.lock().unwrap().in_flight()
162    }
163
164    /// Access the virtual submission ring.
165    pub fn submission_ring(&self) -> &SubmissionRing {
166        &self.sq
167    }
168
169    /// Access the virtual completion ring.
170    pub fn completion_ring(&self) -> &CompletionRing {
171        &self.cq
172    }
173
174    /// Register all buffers currently tracked by `registry` with the OS kernel
175    /// for zero-copy fixed-buffer I/O (io_uring `IORING_REGISTER_BUFFERS`).
176    ///
177    /// After this call, `read`/`write` operations whose buffer matches a
178    /// registered region base will be issued as `IORING_OP_READ_FIXED` /
179    /// `WRITE_FIXED`, skipping per-operation address translation in the kernel.
180    ///
181    /// # Example
182    ///
183    /// ```no_run
184    /// use tpt_torus_core::lease::LeaseRegistry;
185    /// use tpt_torus_core::Torus;
186    /// # fn make_torus() -> Torus { unimplemented!() }
187    /// let torus = make_torus();
188    /// let registry = LeaseRegistry::new();
189    /// let mut buf = vec![0u8; 4096];
190    /// unsafe { registry.register(buf.as_mut_ptr(), buf.len()) };
191    /// torus.register_leases(&registry)?; // enables IORING_OP_READ/WRITE_FIXED
192    /// # Ok::<(), tpt_torus_core::Error>(())
193    /// ```
194    ///
195    /// # Platform notes
196    /// - Linux (io_uring): registers the regions with the kernel immediately.
197    /// - Other platforms: this is a no-op (no fixed-buffer mechanism available).
198    #[cfg(unix)]
199    pub fn register_leases(&self, registry: &LeaseRegistry) -> crate::error::Result<()> {
200        let buffers = registry.as_register_buffers();
201        if buffers.is_empty() {
202            return Ok(());
203        }
204        self.backend.lock().unwrap().register_buffers(&buffers)
205    }
206
207    /// Register lease buffers with the kernel.
208    ///
209    /// No-op on platforms without a fixed-buffer mechanism. See the Unix
210    /// implementation of [`Torus::register_leases`].
211    #[cfg(not(unix))]
212    pub fn register_leases(&self, _registry: &LeaseRegistry) -> crate::error::Result<()> {
213        Ok(())
214    }
215
216    /// Get raw, unguarded access to the Torus, bypassing Buffer Leasing.
217    ///
218    /// # Safety
219    ///
220    /// The returned [`RawTorus`] bypasses all buffer safety checks.
221    /// The caller is responsible for ensuring buffer validity.
222    pub unsafe fn raw(&self) -> raw_api::RawTorus<'_> {
223        raw_api::RawTorus::new(self)
224    }
225}
226
227/// Shared handle to a `Torus` instance, suitable for multi-threaded use.
228pub type SharedTorus = Arc<Torus>;
229
230/// A pool of `Torus` instances that distributes I/O across multiple backends.
231///
232/// `TorusPool` avoids serializing all I/O through a single `Mutex<dyn Backend>`
233/// by maintaining N independent `Torus` instances and distributing operations
234/// across them via round-robin. Each `Torus` in the pool has its own backend
235/// and ring pair, so submissions on different pool entries are fully concurrent.
236///
237/// # Example
238///
239/// ```ignore
240/// use tpt_torus_core::TorusPool;
241///
242/// // Create a pool with 4 Torus instances (one per core)
243/// let pool = TorusPool::new(4, 256, |ring_entries| {
244///     Box::new(UringBackend::new(ring_entries)?)
245/// })?;
246///
247/// // Submit operations — distributed across pool members
248/// pool.submit(&flow)?;
249/// ```
250pub struct TorusPool {
251    instances: Vec<Arc<Torus>>,
252    next: AtomicU32,
253}
254
255impl TorusPool {
256    /// Create a new pool with `count` Torus instances.
257    ///
258    /// Each instance gets `ring_entries` SQ/CQ entries. The `make_backend`
259    /// closure is called once per instance to create the platform-specific backend.
260    pub fn new<F>(count: usize, ring_entries: u32, make_backend: F) -> Result<Self>
261    where
262        F: Fn(u32) -> Result<Box<dyn Backend>>,
263    {
264        if count == 0 {
265            return Err(Error::InvalidParam("pool count must be > 0"));
266        }
267        let mut instances = Vec::with_capacity(count);
268        for _ in 0..count {
269            let backend = make_backend(ring_entries)?;
270            instances.push(Arc::new(Torus::new(ring_entries, backend)?));
271        }
272        Ok(Self {
273            instances,
274            next: AtomicU32::new(0),
275        })
276    }
277
278    /// Submit a flow to the next available Torus instance (round-robin).
279    pub fn submit(&self, flow: &Flow) -> Result<()> {
280        let idx = self.next.fetch_add(1, Ordering::Relaxed) as usize % self.instances.len();
281        self.instances[idx].submit(flow)
282    }
283
284    /// Submit a batch of flows, distributing them across pool instances.
285    pub fn submit_batch(&self, flows: &[Flow]) -> Result<usize> {
286        let mut total = 0;
287        for flow in flows {
288            self.submit(flow)?;
289            total += 1;
290        }
291        Ok(total)
292    }
293
294    /// Reap completions from all pool instances.
295    pub fn reap(&self, results: &mut Vec<TorusResult>) -> Result<usize> {
296        let mut total = 0;
297        for instance in &self.instances {
298            total += instance.reap(results)?;
299        }
300        Ok(total)
301    }
302
303    /// The number of Torus instances in this pool.
304    pub fn len(&self) -> usize {
305        self.instances.len()
306    }
307
308    /// Whether the pool is empty.
309    pub fn is_empty(&self) -> bool {
310        self.instances.is_empty()
311    }
312
313    /// Get a reference to a specific pool instance.
314    pub fn get(&self, index: usize) -> Option<&Torus> {
315        self.instances.get(index).map(|arc| arc.as_ref())
316    }
317
318    /// Total in-flight operations across all pool instances.
319    pub fn in_flight(&self) -> u32 {
320        self.instances.iter().map(|i| i.in_flight()).sum()
321    }
322}