Skip to main content

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}