Skip to main content

EventDelivery

Struct EventDelivery 

Source
pub struct EventDelivery { /* private fields */ }
Expand description

Delivers an IoRing’s completions on thread-pool callback threads via its completion event, rather than a caller polling IoRing::try_pop itself (Model A, D-3).

§Not a CleanupGroup member

There is no way to add an already-built EventDelivery to a CleanupGroup, for the same reason CleanupGroup excludes ThreadpoolIo: the ring must run down its outstanding operations before closing, and a group’s bulk CloseThreadpoolCleanupGroupMembers has no way to run that logic for a member it did not create itself. EventDelivery stays individually owned, where its own field-drop order (below) gives the same quiesce-then-close guarantee a group would otherwise provide.

Implementations§

Source§

impl EventDelivery

Source

pub fn new<F>( ring: IoRing, on_completion: F, env: Option<&mut CallbackEnviron<'_>>, ) -> Result<Self>
where F: Fn(Completion) + Send + Sync + 'static,

Wire ring’s completion event to a thread-pool wait, delivering every popped Completion to on_completion on a pool thread (M4.2).

The wait is armed before this returns, so the calling thread never waits for a completion itself (M4.4).

§Completions already queued when ring is handed over

They are delivered too – but that is a guarantee this method has to buy, not one it inherits, and saying so is the point of this section. The ring’s event is edge-triggered on the completion queue going empty to non-empty (IoRing::completion_event, D-19), so attaching to a ring whose queue is already non-empty signals nothing, and no later completion signals either, because the queue never returns to empty to re-arm the edge. Such a backlog is stranded permanently.

What closes that gap is the deliberate signal IoRing::completion_event raises as it attaches: the first callback then drains the backlog exactly as it would any other wakeup.

This was false in the implementation, and asserted anyway in this rustdoc, before M11.3 – every test until then handed over a fresh ring, so nothing contradicted it. A caller on an earlier version cannot rely on the guarantee; tests/event_delivery.rs keeps the repro that now holds it.

§Errors

Returns io::ErrorKind::Unsupported if the running system does not report IORING_FEATURE_SET_COMPLETION_EVENT (M4.1), which IoRing::completion_event is what decides. This crate refuses to silently substitute a thread-based polling loop instead – a caller who asked for event-driven delivery and got a spun-up thread has been told something false. Also returns any other error from IoRing::completion_event or from ThreadpoolWait::new.

Source

pub fn scope(&self) -> RingScope<'_>

Lock the wrapped ring and return a scope for submitting work to it.

A caller submits by opening a RingScope::batch, the same ring the wait callback locks to pop completions. Mutex poisoning is absorbed here rather than surfaced: a panic elsewhere does not invalidate a ring handle, and the callback’s own drain already takes the same view, so making every caller write unwrap_or_else(PoisonError::into_inner) only invited the inconsistency this method removes.

§What the scope deliberately withholds

The rule is: every read-only part of IoRing, plus batch construction – and nothing that can retarget the ring or steal the pool’s completions. So there is no try_pop (D-21 makes the pool the single drainer; a second one breaks the drain-to-empty that re-arms the edge), no completion_event (it hands back a duplicate of the event this EventDelivery already waits on, giving two waiters on one ring), no run_down, and above all no &mut IoRing.

That last one is the point, and it is why this returns a scope rather than the &Mutex<IoRing> it used to. Any &mut IoRing permits whole-value assignment, so safe code could replace the ring while the pool’s wait stayed armed on the original ring’s event – measured, and delivery stopped silently (D-43). Note a Deref/DerefMut newtype would not have closed that, since *scope = ... works through DerefMut just as well; nor would handing a &mut IoRing to a closure.

Replacing the ring is therefore refused at compile time:

ⓘ
let delivery =
    EventDelivery::new(IoRing::new(8, 8).unwrap(), |_| {}, None).unwrap();
let mut scope = delivery.scope();
// No `DerefMut`, so there is no `&mut IoRing` to assign through.
*scope = IoRing::new(8, 8).unwrap();

The same setup doing the legitimate thing must still compile. A compile_fail example passes on any error, including a typo in its own setup, so this pair is what keeps the one above honest:

let delivery =
    EventDelivery::new(IoRing::new(8, 8).unwrap(), |_| {}, None).unwrap();
let scope = delivery.scope();
assert_eq!(scope.outstanding(), 0);

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.