wireshift_core/backend.rs
1//! Backend abstractions used by the generic ring.
2//!
3//! This module defines the core trait contracts that all backends must implement,
4//! including the [`Backend`] trait for pluggable backends and [`AnyBackend`] for
5//! enum-based zero-cost dispatch.
6
7use crate::error::{Error, Result};
8use crate::op::{CompletionPayload, OpDescriptor};
9use crate::RingConfig;
10use std::sync::mpsc::Sender;
11
12/// The active backend implementation.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14#[non_exhaustive]
15pub enum BackendKind {
16 /// Linux `io_uring`.
17 IoUring,
18 /// Fallback worker backend.
19 Fallback,
20}
21
22impl BackendKind {
23 /// Returns a stable string name.
24 #[must_use]
25 pub fn as_str(self) -> &'static str {
26 match self {
27 Self::IoUring => "io_uring",
28 Self::Fallback => "fallback",
29 }
30 }
31}
32
33/// Boxed backend trait object (legacy, kept for backwards compatibility).
34///
35/// This type alias exists for backwards compatibility with code that uses
36/// `Box<dyn Backend>` directly. New code should prefer [`AnyBackend`] for
37/// better performance through enum dispatch.
38pub type BoxedBackend = Box<dyn Backend>;
39
40/// Backend submission payload.
41///
42/// Carries an operation descriptor from the ring to the backend along with
43/// a stable request identifier used for completion routing and cancellation.
44#[derive(Debug)]
45pub struct BackendSubmission {
46 /// Stable request id.
47 pub id: u64,
48 /// Operation descriptor.
49 pub descriptor: OpDescriptor,
50 /// Optional timeout for this operation.
51 pub timeout: Option<std::time::Duration>,
52}
53
54impl BackendSubmission {
55 /// Constructs a submission value.
56 #[must_use]
57 pub fn new(id: u64, descriptor: OpDescriptor) -> Self {
58 Self {
59 id,
60 descriptor,
61 timeout: None,
62 }
63 }
64
65 /// Sets the timeout for this submission.
66 #[must_use]
67 pub fn with_timeout(mut self, timeout: Option<std::time::Duration>) -> Self {
68 self.timeout = timeout;
69 self
70 }
71}
72
73/// Backend completion payload.
74///
75/// Delivered from the backend to the ring when an operation completes,
76/// successfully or with an error.
77#[derive(Debug)]
78pub struct BackendCompletion {
79 /// Stable request id matching the original submission.
80 pub id: u64,
81 /// Completion result containing the payload or an error.
82 pub result: Result<CompletionPayload>,
83}
84
85/// Cancellation request handle.
86///
87/// Passed to [`Backend::cancel`] to request cancellation of an in-flight operation.
88#[derive(Clone, Copy, Debug)]
89pub struct CancellationHandle {
90 /// Request id to cancel.
91 pub target: u64,
92}
93
94/// Pluggable backend interface.
95///
96/// # Contract
97///
98/// All implementations must satisfy these behavioral contracts:
99///
100/// ## Thread Safety
101/// - [`Backend`] extends [`Send`] + [`Sync`]: implementations must be safe to
102/// share between threads and call concurrently from multiple threads.
103///
104/// ## Submission (`submit`)
105/// - Must be **non-blocking**: the method should return immediately after
106/// queueing the work, not wait for completion.
107/// - Must accept submissions until [`Backend::shutdown`] is called.
108/// - Must return an error if the backend is unable to accept new work.
109/// - The backend is responsible for eventually calling the completion callback
110/// for every successfully submitted operation.
111///
112/// ## Cancellation (`cancel`)
113/// - Best-effort cancellation of in-flight work identified by request id.
114/// - Cancellation may race with completion: both outcomes are valid.
115/// - Returns an error if the target request id is unknown or already complete.
116///
117/// ## Shutdown (`shutdown`)
118/// - Must be **idempotent**: calling multiple times should not panic or error.
119/// - Must gracefully wait for all in-flight operations to complete or cancel.
120/// - After shutdown returns, no new completions should be delivered.
121/// - Should signal any worker threads to exit and wait for them to join.
122pub trait Backend: Send + Sync {
123 /// Returns the backend kind.
124 fn kind(&self) -> BackendKind;
125
126 /// Submits work to the backend.
127 ///
128 /// # Contract
129 /// - This method must be non-blocking.
130 /// - Returns `Ok(())` if the submission was accepted.
131 /// - Returns an error if the backend cannot accept the submission.
132 fn submit(&self, submission: BackendSubmission) -> Result<()>;
133
134 /// Requests cancellation for in-flight work.
135 ///
136 /// # Contract
137 /// - Best-effort: the operation may complete before cancellation takes effect.
138 /// - Returns an error if the target request is not found.
139 fn cancel(&self, cancellation: CancellationHandle) -> Result<()>;
140
141 /// Shuts the backend down.
142 ///
143 /// # Contract
144 /// - Idempotent: safe to call multiple times.
145 /// - Waits for all in-flight operations to complete.
146 /// - No new completions are delivered after this returns.
147 fn shutdown(&self) -> Result<()>;
148}
149
150/// Enum dispatch backend for eliminating vtable indirection on the hot path.
151///
152/// # Enum Dispatch vs Vtable
153///
154/// This enum uses **enum dispatch** (also known as "inline dispatch") to avoid
155/// vtable lookups for the built-in backends:
156///
157/// | Approach | Pros | Cons |
158/// |----------|------|------|
159/// | **Enum dispatch** (`AnyBackend`) | Zero-cost: no vtable indirection, better inlining, cache-friendly | Closed set of variants |
160/// | **Vtable** (`Box<dyn Backend>`) | Open for extension, dynamic plugin loading | Virtual call overhead, harder to inline |
161///
162/// ## Migration Path: Adding a New Backend (e.g., kqueue)
163///
164/// 1. **Create a new crate**: `kqueue/Cargo.toml`
165/// 2. **Implement the trait**: `impl Backend for KqueueBackend { ... }`
166/// 3. **Add the variant** (optional, for zero-cost):
167/// ```rust
168/// // In core/src/backend.rs
169/// pub enum AnyBackend {
170/// Boxed(BoxedBackend),
171/// Kqueue(wireshift_kqueue::KqueueBackend), // Zero-cost variant
172/// }
173/// ```
174/// 4. **Feature-flag it** in the workspace Cargo.toml:
175/// ```toml
176/// [features]
177/// kqueue = ["dep:wireshift-kqueue"]
178/// ```
179///
180/// ## Send + Sync Guarantees
181///
182/// `AnyBackend` is `Send + Sync` because every variant contains only `Send + Sync`
183/// types. The compiler automatically derives these traits - no `unsafe impl` needed.
184/// If a future variant breaks this contract, the compiler will reject it.
185#[non_exhaustive]
186pub enum AnyBackend {
187 /// A boxed backend (legacy or third-party).
188 ///
189 /// Use this for foreign backends that aren't part of the core wireshift
190 /// workspace. For built-in backends, prefer dedicated variants for
191 /// zero-cost dispatch.
192 Boxed(BoxedBackend),
193}
194
195impl AnyBackend {
196 /// Wrap a boxed backend into the enum dispatch.
197 #[must_use]
198 pub fn from_boxed(backend: BoxedBackend) -> Self {
199 Self::Boxed(backend)
200 }
201}
202
203impl Backend for AnyBackend {
204 fn kind(&self) -> BackendKind {
205 match self {
206 Self::Boxed(b) => b.kind(),
207 }
208 }
209
210 fn submit(&self, submission: BackendSubmission) -> Result<()> {
211 match self {
212 Self::Boxed(b) => b.submit(submission),
213 }
214 }
215
216 fn cancel(&self, cancellation: CancellationHandle) -> Result<()> {
217 match self {
218 Self::Boxed(b) => b.cancel(cancellation),
219 }
220 }
221
222 fn shutdown(&self) -> Result<()> {
223 match self {
224 Self::Boxed(b) => b.shutdown(),
225 }
226 }
227}
228
229/// Backend construction hook used by [`crate::Ring`].
230///
231/// Implementations provide a way to create backends with specific configurations.
232/// The factory pattern allows rings to be generic over backend selection while
233/// still supporting configuration-specific backend creation.
234///
235/// # Example
236///
237/// ```rust,ignore
238/// #[derive(Default)]
239/// struct MyBackendFactory;
240///
241/// impl BackendFactory for MyBackendFactory {
242/// fn create(
243/// &self,
244/// config: &RingConfig,
245/// completion_tx: Sender<BackendCompletion>,
246/// ) -> Result<BoxedBackend> {
247/// Ok(Box::new(MyBackend::new(config, completion_tx)?))
248/// }
249/// }
250/// ```
251pub trait BackendFactory: Send + Sync + Default + 'static {
252 /// Creates the backend for the given ring configuration (legacy API).
253 ///
254 /// # Errors
255 ///
256 /// Returns an error if the backend cannot be created (e.g., unsupported
257 /// platform, resource exhaustion, invalid configuration).
258 fn create(
259 &self,
260 config: &RingConfig,
261 completion_tx: Sender<BackendCompletion>,
262 ) -> Result<BoxedBackend>;
263
264 /// Creates the backend as an enum-dispatched value.
265 ///
266 /// The default implementation wraps the result of [`create`](Self::create)
267 /// into [`AnyBackend::Boxed`]. Override this in concrete factories to
268 /// return specific variants for zero-cost dispatch.
269 ///
270 /// # Errors
271 ///
272 /// Returns an error if the backend cannot be created.
273 fn create_enum(
274 &self,
275 config: &RingConfig,
276 completion_tx: Sender<BackendCompletion>,
277 ) -> Result<AnyBackend> {
278 self.create(config, completion_tx)
279 .map(AnyBackend::from_boxed)
280 }
281}
282
283/// Returns a shared error when a backend worker channel disconnects.
284#[must_use]
285pub fn disconnected_error() -> Error {
286 Error::completion(
287 "backend worker channel disconnected",
288 "keep backend worker threads alive until the ring is dropped",
289 )
290}