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}