Skip to main content

RegisteredBuffers

Struct RegisteredBuffers 

Source
pub struct RegisteredBuffers<B: IoBufMut> { /* private fields */ }
Expand description

Buffers registered with a ring so later reads and writes can address them by index instead of handing over a fresh owned buffer each call (M5.2).

Win32’s IoRing has no unregister call at all: once registered, an index is valid for the ring’s remaining life. This type’s own job is narrower than reproducing that – refusing to let its backing memory be freed while this crate still has an operation outstanding against it (M5.3) – not proving an index will never be touched again by some later, differently-built SQE. That wider hazard belongs to crate::IoRing::push_raw’s existing unsafe contract, the same as any raw use of an index this crate handed out.

Implementations§

Source§

impl<B: IoBufMut> RegisteredBuffers<B>

Source

pub fn len(&self) -> u32

How many buffers this registration holds.

Source

pub fn is_empty(&self) -> bool

Whether this registration holds no buffers.

Always false in practice, and a mutation run reports the constant as surviving for that reason rather than for want of a test. Nothing in this crate rejects an empty vector, but the kernel refuses the submission with E_INVALIDARG, so a caller never holds an empty registration. windows_refuses_an_empty_buffer_registration pins that platform behaviour; if a future Windows accepts it, that test fails and this becomes reachable.

Kept because it is half of the len/is_empty pair every Rust collection surface offers, and because “cannot happen today” is a weaker claim than “cannot happen”.

Source

pub fn get(&mut self, i: u32) -> Result<&[u8]>

Borrow the i-th buffer’s registered bytes, for reading a completed read’s or write’s bytes without going through the ring at all.

§Why it can refuse

Because otherwise it is a data race with the kernel, reachable from entirely safe code. Batch::read_registered_owned takes &RegisteredBuffers and holds only a RegisteredUse – no borrow of this registration outlives the push. So without the check below, this sequence compiles with no unsafe anywhere and reads memory the kernel is concurrently writing:

ⓘ
batch.read_registered_owned(&file, &arena, span, (), 0, PushOptions::new())?;
batch.submit()?;                 // kernel now filling that buffer
let bytes = arena.get(span.buffer_index)?;

The reverse order is the one the check alone could not stop, and is why this takes &mut self – see below:

ⓘ
let bytes = arena.get(0)?;       // quiet here, so the check passes
batch.read_registered_owned(&file, &arena, span, (), 0, PushOptions::new())?;
batch.submit()?;                 // kernel now filling that same buffer
let stale = bytes[0];            // ... read through the live borrow
§Why this takes &mut self to hand back a shared slice

The refusal above is a check at one instant, but the slice it returns lives as long as the borrow. Taking &self made those two different lengths of time, and the gap was reachable: read_registered takes the registration by shared reference, so a caller could take the borrow while the buffer was quiet, then push and submit a read into that same buffer and keep reading through the still-live slice. Measured, not argued – a probe watched the bytes change under a live &[u8] while a fresh get at that same instant correctly refused.

&mut self closes it by making the borrow conflict with starting the operation, not merely its creation: read_registered(&arena, ..) cannot be called while this slice is alive. That costs nothing real, because no caller needs to read a buffer during the window it is refused. While a read is in flight the bytes are indeterminate – partially written, in arbitrary order – and only become meaningful once the completion is observed. Earlier or later is always available.

It does not restrict an arena. An entry holds a RegisteredUse, not a borrow of this registration, so the shared borrow ends when the push returns; a caller may still read or fill quiet buffers while operations are outstanding against their neighbours, which is what makes a registered arena usable at all.

§Why it refuses less than get_mut does

get_mut refuses while any operation is outstanding, because mutating races the kernel whichever way the kernel is touching the buffer. Reading is narrower: it races only a kernel that is writing into the buffer, so this refuses only while a read is outstanding – the bytes of a write in flight are stable, and the kernel is reading them too.

A count returns to zero only when a completion is popped against a real popped Completion, so “not outstanding” means the operation was observed to finish, never merely assumed to have.

§Why this hands back &[u8] and not &B

For the same reason get_mut does: the slice is exactly the length handed to BuildIoRingRegisterBuffers, which is what the kernel knows about. B’s own bytes_len() is a live value that may since have diverged from it, so answering from the registered length keeps this in step with the registration rather than with the buffer.

§Holding the borrow across a push is refused at compile time

This is the regression guard for the hazard described above. Taking the borrow while the buffer is quiet and then submitting a read into it must not compile, and the failure it exists for is the borrow conflict, E0502. That code is pinned below, with a limit worth knowing: rustdoc checks a pinned code only on a nightly toolchain (or under RUSTC_BOOTSTRAP=1, which CI’s doctest-error-codes job sets), and on stable a compile_fail example passes on any error. This one went on “passing” after read_registered was removed, because calling a method that does not exist fails to compile too; the no_run twin below, which must compile, is what catches a rename that reaches both:

ⓘ
let bytes: &[u8] = arena.get(0)?;      // quiet here, so the check passes
let mut batch = Batch::new(ring);
batch.read_registered_owned(file, arena, span, (), 0, PushOptions::new())?;
batch.submit()?;                       // kernel now filling that buffer
Ok(bytes[0])                           // ... read through the live borrow

Reading a quiet buffer while an operation is outstanding against a neighbour is still allowed, because the ring’s hold on a registration is per-buffer – that is the arena pattern, and it must keep working:

let mut batch = Batch::new(ring);
batch.read_registered_owned(file, arena, span, (), 0, PushOptions::new())?;
batch.submit()?;
let _neighbour = arena.get(1)?;        // a different, quiet buffer
§Errors

io::ErrorKind::InvalidInput if i is out of range for this registration, or io::ErrorKind::WouldBlock if a read is still outstanding into that buffer – pop its completion first, which is what returns the buffer.

Source

pub fn get_mut(&mut self, i: u32) -> Result<&mut [u8]>

Borrow the i-th buffer’s registered bytes mutably, to fill them before an operation reads out of them (M13.2).

This is what makes a registered arena usable for writes a caller composes itself – a log record, a serialized message – rather than only for bytes the kernel put there. Without it, get can show a caller what a read landed but nothing can put anything into a buffer after it is registered.

§Why this hands back &mut [u8] and not &mut B

Because &mut B would be unsound, and quietly so. Win32’s IoRing has no unregister call: the address and length handed to BuildIoRingRegisterBuffers stay live for the ring’s remaining life. A &mut Vec<u8> lets safe code assign a whole new vector, reserve, or resize – each of which frees or moves the very allocation the kernel still holds the address of. No unsafe is involved, and it happens at a moment when nothing is outstanding, so no runtime check would catch it. IoBuf’s contract requires that address to stay put for as long as this crate holds the value, and a registration holds it forever.

A byte slice of exactly the registered length grants the one power a caller actually needs – writing the bytes – and none of the powers that would break the registration.

§Why it can refuse

&mut self is not on its own enough either. It excludes concurrent &self borrows, but an operation already in flight holds no borrow at all – Batch::write_registered_owned takes &RegisteredBuffers for the length of the call, and the entry it creates keeps only a RegisteredUse. So the compiler would happily allow mutation of a buffer the kernel is reading through right now, which is a data race with the kernel.

The runtime half of the check is therefore the per-buffer outstanding count: this refuses while any operation is still outstanding against this buffer. Neighbouring buffers being in flight is fine, which is the point of counting per buffer – an arena a caller refills has to be able to prepare buffer 5 while buffer 2 is still being written.

A count returns to zero only when a completion is popped against a real popped Completion, so “not outstanding” means the operation was observed to finish, never merely assumed to have.

§Errors

io::ErrorKind::InvalidInput if i is out of range for this registration, or io::ErrorKind::WouldBlock if an operation is still outstanding against that buffer – pop its completion first, which is what returns the buffer.

Source

pub fn outstanding(&self, i: u32) -> Option<usize>

How many operations are still outstanding against the i-th buffer, or None if i is out of range.

Lets a caller pick a quiet buffer without provoking an error from RegisteredBuffers::get_mut, which is the normal shape for an arena cycling through its slots.

Source

pub fn check_span(&self, span: RegisteredSpan) -> Result<()>

Whether span lies within this registration – its buffer index in range, and span.offset .. span.offset + span.len within that buffer’s registered length – asked without pushing anything. It is the check every push against a span makes before the kernel sees it, so the answer is the push’s (M34.1).

For a caller that accepts an operation now and pushes it later, so it can refuse a span the push would refuse at the moment it accepts it, rather than at a push nobody is waiting on.

§Errors

The io::ErrorKind::InvalidInput error a push would refuse span with.

Trait Implementations§

Source§

impl<B: IoBufMut> Drop for RegisteredBuffers<B>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

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, !>

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.