Skip to main content

wireshift_core/strategy/
batch.rs

1//! Configures accumulation across multiple ops prior to pushing to kernel.
2use std::time::Duration;
3
4use crate::completion::CompletionEvent;
5use crate::error::{Error, Result};
6use crate::{BackendFactory, Ring};
7
8/// Drains up to `limit` completions in one call.
9///
10/// ```no_run
11/// use wireshift::{Ring, RingConfig, strategy::BatchDrain};
12///
13/// let ring = Ring::new(RingConfig::default())?;
14/// let drain = BatchDrain::new(ring, 16);
15/// let _ = drain;
16/// # Ok::<(), Box<dyn std::error::Error>>(())
17/// ```
18#[derive(Clone)]
19pub struct BatchDrain<F: BackendFactory> {
20    ring: Ring<F>,
21    limit: usize,
22}
23
24impl<F: BackendFactory> BatchDrain<F> {
25    /// Creates a batch drain adapter.
26    #[must_use]
27    pub fn new(ring: Ring<F>, limit: usize) -> Self {
28        Self {
29            ring,
30            limit: limit.max(1),
31        }
32    }
33
34    /// Attempts to drain up to `limit` completions within the optional timeout budget.
35    #[must_use]
36    pub fn drain(&self, timeout: Option<Duration>) -> Vec<Result<CompletionEvent>> {
37        let mut completions = Vec::with_capacity(self.limit);
38        for index in 0..self.limit {
39            // First completion: use caller's timeout (may block).
40            // Subsequent: zero-wait poll. At internet scale doing millions of
41            // completions per second, even 1ms sleep between completions is
42            // catastrophic. We drain everything that's ready, then return.
43            let wait = if index == 0 {
44                timeout
45            } else {
46                Some(Duration::ZERO)
47            };
48            match self.ring.complete(wait) {
49                Ok(event) => completions.push(Ok(event)),
50                // Non-first completion timeout is EXPECTED - it just means no more
51                // completions are ready right now, so stop draining quietly.
52                Err(Error::Timeout { .. }) if index > 0 => break,
53                // Anything else - a first-completion error, or a non-timeout fatal
54                // backend error on a later completion - must be surfaced, not
55                // silently broken out of (Law 10). The old code discarded every
56                // non-first error, hiding fatal backend failures mid-batch.
57                Err(error) => {
58                    completions.push(Err(error));
59                    break;
60                }
61            }
62        }
63        completions
64    }
65}