Skip to main content

Batch

Struct Batch 

Source
pub struct Batch<'ring, T = (), X = (), K = Open> { /* private fields */ }
Expand description

A scoped, exclusive submission window over one IoRing (D-5).

Holding &mut IoRing for its lifetime is what turns Win32’s own “you must serialize submission” footnote into a compiler-enforced guarantee: two batches over the same ring cannot coexist. Every push queues its SQE the instant it succeeds – there is no rewind – so Batch submits on Drop rather than leaving queued SQEs for some later, unrelated submit to discover (D-5).

Implementations§

Source§

impl<'ring, T, X, K> Batch<'ring, T, X, K>

Source

pub unsafe fn write_raw_owned( &mut self, file: impl Into<FileRef>, buffer: T, extra: X, offset: u64, options: PushOptions, caching: WriteCaching, ) -> Result<OperationId, PushRefused<T, X>>
where T: IoBuf,

Queue a write of buffer.bytes_len() bytes to file at offset, whose buffer the ring holds (D-71, D-73).

The raw counterpart to Batch::write_owned: prefer that unless file needs to address a raw FileRef directly, without a SharedHandle’s shared-ownership bookkeeping.

§Safety

If file is FileRef::Raw, the handle must be valid, opened with write access, and must remain valid – not closed, not reused for a different object – until this operation’s completion is observed (via a popped Completion) or until the ring runs down (M8, PR #20 review response). A FileRef::Registered target needs none of this.

§Errors

io::ErrorKind::Unsupported if the ring was not probed as supporting Op::Write; io::ErrorKind::InvalidInput if the buffer is longer than u32::MAX; an crate::IoRingError wrapping IORING_E_SUBMISSION_QUEUE_FULL if the queue has no room (M3.3, not auto-flushed – see Batch’s own docs); or any other error from BuildIoRingWriteFile. On any error the buffer and extra are handed back in the PushRefused (D-80).

Source

pub fn read_owned<F: FileTarget>( &mut self, file: &F, buffer: T, extra: X, offset: u64, options: PushOptions, ) -> Result<OperationId, PushRefused<T, X>>
where T: IoBufMut,

Queue a read against a guarded file, with the ring holding both the buffer and the guard (D-73).

The guarded counterpart to Batch::read_raw_owned. The guard goes into the ring’s own slot rather than into T, which is what keeps the caller’s parameters free of this crate’s internals.

§Errors

As Batch::read_raw_owned.

Source

pub fn write_owned<F: FileTarget>( &mut self, file: &F, buffer: T, extra: X, offset: u64, options: PushOptions, caching: WriteCaching, ) -> Result<OperationId, PushRefused<T, X>>
where T: IoBuf,

Queue a write against a guarded file, with the ring holding both the buffer and the guard (D-73).

The guarded counterpart to Batch::write_raw_owned.

§Errors

As Batch::write_raw_owned.

Source

pub unsafe fn read_raw_owned( &mut self, file: impl Into<FileRef>, buffer: T, extra: X, offset: u64, options: PushOptions, ) -> Result<OperationId, PushRefused<T, X>>
where T: IoBufMut,

Queue a read whose buffer the ring holds, returning the operation’s name (D-71, D-73).

The raw counterpart to Batch::read_owned: prefer that unless file needs to address a raw FileRef directly, without a SharedHandle’s shared-ownership bookkeeping. The caller hands over the buffer and its sidecar and receives an OperationId, which names the operation and grants nothing; the buffer comes back from IoRing::try_pop and from nowhere else. A consumer that never holds a token cannot lose one, which is the whole of D-55.

§Safety

If file is FileRef::Raw, the handle must be valid, opened with read access, and must remain valid – not closed, not reused for a different object – until this operation’s completion is observed (via a popped Completion) or until the ring runs down (M8, PR #20 review response). A FileRef::Registered target needs none of this.

§Errors

io::ErrorKind::Unsupported if the ring was not probed as supporting Op::Read; io::ErrorKind::InvalidInput if the buffer is longer than u32::MAX; an crate::IoRingError wrapping IORING_E_SUBMISSION_QUEUE_FULL if the queue has no room (M3.3, not auto-flushed – see Batch’s own docs); or any other error from BuildIoRingReadFile. On any error the buffer and extra are handed back in the PushRefused (D-80): a refused push queued no SQE, so nothing will ever complete to return them, and the refusal is the only way back.

Source

pub unsafe fn flush_raw( &mut self, file: impl Into<FileRef>, coverage: FlushCoverage, mode: FlushMode, ) -> Result<usize>
where K: AllowsRaw,

Queue a flush of file’s buffered data.

coverage decides whether this flush covers the operations queued before it. It is required rather than defaulted because there is no safe default – see FlushCoverage and the measured contract below.

mode is FILE_FLUSH_MODE; FlushMode::Default is the durability barrier. Note that FlushMode::NoSync issues no device sync and so makes nothing durable, whatever coverage says.

There is no buffer, so this returns the raw UserData identity rather than an inventory entry: nothing owns a buffer for a completion to hand back. Prefer Batch::flush_owned unless file needs to address a raw FileRef directly.

§A flush is the ring’s only durability primitive

The ring has no FUA. BuildIoRingWriteFile’s entire flag set is {FILE_WRITE_FLAGS_NONE, FILE_WRITE_FLAGS_WRITE_THROUGH}, and write-through is a cache-bypass directive to the OS rather than a device-level durability guarantee – whether it becomes a Force Unit Access bit depends on the driver, the volume, and whether the device’s write cache is enabled (see WriteCaching). So this operation is the only way the ring makes anything durable, and only with FlushCoverage::CoversPrecedingOperations and a syncing FlushMode.

§The measured contract (D-23 in DESIGN-NOTES.md)

An unflagged flush does not cover preceding writes. It is an ordinary operation competing with them, and it frequently wins: a flush pushed after a batch of writes with no barrier was observed completing while 17, and on another run 23, of 32 of those writes were still outstanding. A caller that reads such a completion as “the writes before it are now durable” has lost data it believes it has committed, and nothing reports the loss until power fails.

Which direction the reordering shows in is device-dependent, and a machine where the flush happens to land last anyway proves nothing: that is incidental behavior of one device stack, not a guarantee. Only the barrier makes it one.

Durability on this ring is therefore a property of an epoch, never of an individual write, because there is no per-write primitive to make it one: stream the writes unflagged, close the epoch with one covering flush, and wait on that flush rather than on the writes. “Durability on the ring” in DESIGN-NOTES.md has the full construction, and the three ways to pay for the barrier’s ring-wide wait.

§Safety

As Batch::read_raw_owned’s, for a FileRef::Raw target.

§Errors

io::ErrorKind::Unsupported if the ring was not probed as supporting Op::Flush; an crate::IoRingError wrapping IORING_E_SUBMISSION_QUEUE_FULL if the queue has no room; or any other error from BuildIoRingFlushFile.

Source

pub unsafe fn flush_raw_owned( &mut self, file: impl Into<FileRef>, extra: X, coverage: FlushCoverage, mode: FlushMode, ) -> Result<OperationId, PushRefused<T, X>>

Queue a raw flush, with the ring holding the sidecar (D-73).

The inventory counterpart to Batch::flush_raw. There is no buffer and no guard – a raw handle is the caller’s to keep alive, exactly as for Batch::flush_raw – so the entry exists purely to carry extra. That is not a degenerate case: a flush that cannot say which group of writes it belongs to forces the caller back into the side-table this API exists to remove.

§Safety

As Batch::flush_raw.

§Errors

As Batch::flush_raw.

Source

pub fn flush_owned<F: FileTarget>( &mut self, file: &F, extra: X, coverage: FlushCoverage, mode: FlushMode, ) -> Result<OperationId, PushRefused<T, X>>

Queue a flush, with the ring holding the file guard (D-73).

The guarded counterpart to Batch::flush_raw_owned. This push stows an inventory entry, so the pop that observes its completion returns that entry – IoRing::try_pop yields Some((completion, Some((None, extra)))). Only the inner payload is None, because there is no buffer; the sidecar still arrives. An outer None is what a _raw flush produces instead – see IoRing::try_pop’s “What each None means”. The guard still has to outlive the operation, and the ring is what holds it.

§Errors

As Batch::flush_raw_owned.

Source

pub fn cancel_owned<F: FileTarget>( &mut self, file: &F, target: OperationId, extra: X, ) -> Result<OperationId, PushRefused<T, X>>

Queue a cancellation of target, with the ring holding the file guard (D-73).

The inventory counterpart to Batch::cancel_raw.

target is an OperationId rather than a bare UserData, and this checks that this ring minted it. Every ring hands out UserData from its own counter starting at the same value, so one integer legitimately names a different operation on each ring – which is why an OperationId carries the ring that minted it alongside the UserData, and why that half must not be discarded. Accepting the bare integer would therefore let an identity from one ring cancel an unrelated operation on another, silently, and most readily when both address the same file. This is the same check Batch already applies to a RegisteredFile belonging to the wrong ring.

To cancel a UserData this ring never minted an OperationId for, use Batch::cancel_owned_raw, which says so at the call site.

§Errors

io::ErrorKind::InvalidInput if target was minted by a different ring; otherwise as Batch::cancel_raw.

Source

pub fn cancel_owned_raw<F: FileTarget>( &mut self, file: &F, target: usize, extra: X, ) -> Result<OperationId, PushRefused<T, X>>

Batch::cancel_owned against a raw UserData, with no ring check.

For the targets an OperationId cannot express: a UserData observed out of band, or – as this crate’s own tests use it – one chosen precisely because nothing is outstanding under it, to exercise the ERROR_NOT_FOUND the kernel reports through the cancel’s own completion.

Safe rather than unsafe, because a cancel returns no memory: naming the wrong operation costs a wrong cancellation, not a use-after-free. It is a separate method so that cost is visible at the call site instead of hidden inside an integer conversion.

§Errors

As Batch::cancel_raw.

Source

pub unsafe fn cancel_raw( &mut self, file: impl Into<FileRef>, target: usize, ) -> Result<usize>
where K: AllowsRaw,

Queue cancellation of the operation identified by target (the usize a prior push returned), against file.

A cancel is itself an operation: it completes on its own UserData, returned here, independently of whether target was actually outstanding. Cancelling a target that has already completed – or was never outstanding – reports ERROR_NOT_FOUND through this completion rather than failing to build (M3.6). Prefer Batch::cancel_owned unless file needs to address a raw FileRef directly.

§Safety

As Batch::read_raw_owned’s, for a FileRef::Raw target.

§Errors

io::ErrorKind::Unsupported if the ring was not probed as supporting Op::Cancel; an crate::IoRingError wrapping IORING_E_SUBMISSION_QUEUE_FULL if the queue has no room; or any other error from BuildIoRingCancelRequest.

Source

pub unsafe fn register_files( &mut self, handles: &[HANDLE], ) -> Result<PendingFileRegistration>
where K: AllowsRaw,

Queue registration of handles as a ring’s file-handle table (M5.1).

BuildIoRingRegisterFileHandles replaces the ring’s entire file-handle table rather than appending to it (Win32 docs: “If a previous registration exists, this replaces the previous registration completely”), which would silently invalidate every RegisteredFile index a prior registration handed out. Rather than track and resubmit that whole prior table transparently, this method refuses a second registration outright: a ring accepts at most one file-handle registration that assigned an index in its lifetime.

Two consequences of that rule being enforced against crate::IoRing::registered_file_count rather than a flag (M10.1): a zero-length handles does not spend the ring’s one registration, since it hands out no index for a later replacement to invalidate; and the count advances when this call queues, not when its completion succeeds (D-14), so a registration whose completion reports failure has still spent it. There is no retry – a consumer whose registration fails must build the registration on a new ring.

The handles slice only needs to stay valid for this call. The kernel reads the array when the registration op runs, during a later SubmitIoRing, not when BuildIoRingRegisterFileHandles returns (D-32, measured) – so this copies it into the ring, which holds the copy until it can prove the op has run, and builds the SQE from that. The handles themselves must still stay open for as long as the registration is used – this crate does not take ownership of them, only of their assigned indices’ bookkeeping.

Before PR #113 this said the opposite, that the kernel reads the array synchronously, and built the SQE from the caller’s slice. A temporary &[handle] then registered whatever its reused stack slot held by the time of the submit: correct in a debug build by accident, and ERROR_INVALID_HANDLE on the first read in a release one.

§Safety

Every handle in handles must be valid, and must remain valid for as long as the resulting registration is used – for the ring’s remaining life, since Win32 has no unregister call (M8, PR #20 review response). Prefer Batch::register_shared_files, the safe counterpart, which discharges this by having the ring own the handles for its whole life (D-81). This method remains for a caller whose handles cannot be SharedHandles.

§Errors

io::ErrorKind::AlreadyExists if this ring already has a file-handle registration (see above); io::ErrorKind::Unsupported if the ring was not probed as supporting Op::RegisterFiles; io::ErrorKind::InvalidInput if handles has more than u32::MAX entries; an crate::IoRingError wrapping IORING_E_SUBMISSION_QUEUE_FULL if the queue has no room; or any other error from BuildIoRingRegisterFileHandles.

Source

pub fn register_shared_files( &mut self, files: Vec<SharedHandle>, ) -> Result<PendingFileRegistration, PushRefused<Vec<SharedHandle>, ()>>
where K: AllowsRaw,

Queue registration of files as a ring’s file-handle table, with the ring holding every file for the rest of its life (M30.2, D-81).

The safe counterpart to Batch::register_files, and the same registration in every other respect: one per ring, shared with that method, so a ring has at most one file registration whichever call made it; a zero-length files spends nothing; the i-th file is RegisteredFiles::get(i) once the registration is claimed.

register_files is unsafe because Win32 has no unregister, so the handles must stay open for the ring’s remaining life, and nothing a single push holds lasts that long. The ring itself does: it keeps files beside the arrays the kernel reads late, and its Drop releases them only after CloseIoRing, and only once rundown has proved nothing is outstanding – otherwise it leaks them, as it does every other thing the kernel may still be using (D-73). A caller may drop its own clones as soon as this returns.

A registration whose completion reports failure has still spent the ring’s one registration, and the ring keeps the files anyway: it cannot prove the kernel took no reference to them.

§Errors

As Batch::register_files. On any error files is handed back in the PushRefused: a refused registration queued nothing, so the kernel never saw a handle.

Source

pub fn register_buffers<B: IoBufMut>( &mut self, buffers: Vec<B>, ) -> Result<PendingBufferRegistration<B>, PushRefused<Vec<B>, ()>>
where K: AllowsRaw,

Queue registration of buffers as a ring’s registered-buffer table (M5.2).

As Batch::register_files: BuildIoRingRegisterBuffers replaces the ring’s entire buffer table rather than appending to it, so this method refuses a second registration outright – a ring accepts at most one buffer registration that assigned an index in its lifetime, with the same zero-length and failed-registration consequences Batch::register_files spells out (M10.1).

Two things must outlive this call (D-32):

  • the bytes each entry points at – the registration case IoBuf’s contract was extended to cover (D-11), so buffers is taken by value and kept inside the returned RegisteredBuffers once claimed;
  • the IORING_BUFFER_INFO array itself, which the kernel reads when the registration op runs, during a later SubmitIoRing – as it does Batch::register_files’ handle array. This crate builds that array and hands it to the ring, which holds it until it can prove the op has run, so a caller has nothing to do – but it is why crate::IoRing::push_raw callers building either registration op themselves must not pass a temporary.
§Errors

As Batch::register_files, for Op::RegisterBuffers and BuildIoRingRegisterBuffers, plus io::ErrorKind::InvalidInput if any buffer is longer than u32::MAX. On any error buffers is handed back whole in the PushRefused (D-80): the kernel never saw an address from a refused registration.

Source

pub unsafe fn read_registered_raw_owned<B: IoBufMut>( &mut self, file: impl Into<FileRef>, registration: &RegisteredBuffers<B>, span: RegisteredSpan, extra: X, file_offset: u64, options: PushOptions, ) -> Result<OperationId, PushRefused<T, X>>

Queue a read of span.len bytes from file at file_offset, into span’s byte offset of registration’s buffer at span.buffer_index, instead of handing over a fresh owned buffer (M5.2), with the ring holding the registration’s use count (D-73).

The raw counterpart to Batch::read_registered_owned: prefer that unless file needs to address a raw FileRef directly.

The buffer belongs to the registration rather than to the caller, so there is no payload to give back – what the ring holds is the use, which keeps the registration from being torn down while the kernel is writing into it. That is Held.registration, released at the pop that completes this operation (M5.3). Read the transferred bytes back from registration itself afterward, for example via a caller-side accessor into the buffer it was constructed from.

§Safety

As Batch::read_raw_owned’s.

§Errors

As Batch::read_raw_owned, plus io::ErrorKind::InvalidInput if span.buffer_index is out of range for registration.

Source

pub fn read_registered_owned<B: IoBufMut, F: FileTarget>( &mut self, file: &F, registration: &RegisteredBuffers<B>, span: RegisteredSpan, extra: X, file_offset: u64, options: PushOptions, ) -> Result<OperationId, PushRefused<T, X>>

Queue a read into a registered buffer against a guarded file, with the ring holding both the use and the guard (D-73).

The guarded counterpart to Batch::read_registered_raw_owned, and the only shape that fills both halves of Held.

§Errors

As Batch::read_registered_raw_owned.

Source

pub unsafe fn write_registered_raw_owned<B: IoBufMut>( &mut self, file: impl Into<FileRef>, registration: &RegisteredBuffers<B>, span: RegisteredSpan, extra: X, file_offset: u64, options: PushOptions, caching: WriteCaching, ) -> Result<OperationId, PushRefused<T, X>>

Queue a write of span.len bytes to file at file_offset, from span’s byte offset of registration’s buffer at span.buffer_index (M5.2).

As Batch::read_registered_raw_owned, but for BuildIoRingWriteFile. Prefer Batch::write_registered_owned unless file needs to address a raw FileRef directly.

§Safety

As Batch::read_registered_raw_owned’s.

§Errors

As Batch::read_registered_raw_owned.

§Why the long parameter list

These are BuildIoRingWriteFile’s own parameters, plus the sidecar the inventory carries. Collapsing them into a struct would hide which are the kernel’s and which are this crate’s, which is the distinction a reader of a push most needs. sys.rs takes the same view for the same reason.

Source

pub fn write_registered_owned<B: IoBufMut, F: FileTarget>( &mut self, file: &F, registration: &RegisteredBuffers<B>, span: RegisteredSpan, extra: X, file_offset: u64, options: PushOptions, caching: WriteCaching, ) -> Result<OperationId, PushRefused<T, X>>

Queue a write from a registered buffer against a guarded file, with the ring holding both the use and the guard (D-73).

The guarded counterpart to Batch::write_registered_raw_owned.

§Errors

As Batch::write_registered_raw_owned.

§Why the long parameter list

These are BuildIoRingWriteFile’s own parameters, plus the sidecar the inventory carries. Collapsing them into a struct would hide which are the kernel’s and which are this crate’s, which is the distinction a reader of a push most needs. sys.rs takes the same view for the same reason.

Source§

impl<'ring, T, X, K> Batch<'ring, T, X, K>

Source

pub fn new(ring: &'ring mut IoRing<T, X, K>) -> Self

Open a batch over ring. The batch has the ring’s kind: over an Owned ring it has no call that queues an operation the ring holds nothing for (D-91).

Source

pub fn submit(self) -> Result<u32>

Submit everything queued so far, returning the number of entries the kernel accepted.

That count is entries submitted, never entries completed (M10.2): draining is crate::IoRing::try_pop’s job and stays the caller’s.

§Errors

Returns any error from SubmitIoRing. An error means the entries were not submitted and remain in the submission queue, which is the documented behaviour of that call: “If this function returns an error other than IORING_E_WAIT_TIMEOUT, then all entries remain in the submission queue.” They are not lost and they are not rewound (D-5) – a later submit on this ring is what runs them, so the buffers they reference must stay alive. Whether and when to submit again is the caller’s policy, not this crate’s.

Source

pub fn leave_queued(self)

Close the batch without submitting what it queued: the entries stay in the submission queue, held by the ring with their buffers, until a later Batch::submit – over a batch that queues nothing, if need be – issues them (D-90).

A batch submits as it drops by default (D-5), so that nothing is left queued for a later, unrelated submit to discover. This is the caller’s explicit choice to own the doorbell instead, to ring it once for several pushes, or to learn that it failed: the drop’s own submit reports nothing. It is the state a failed Batch::submit leaves, reached on purpose. Until the submit, the kernel has not seen the entries, so none will complete.

Source

pub fn submit_and_wait( self, wait_operations: u32, timeout_ms: u32, ) -> Result<u32>

Submit everything queued so far, then block until at least wait_operations of them have completed or timeout_ms elapses (D-3’s Model B primitive: the fused submit-and-wait is the event loop for a pinned-thread consumer).

Returning does not mean wait_operations completions are poppable: the timeout can expire first, and the returned count is entries submitted rather than completed (M10.2). Drain with crate::IoRing::try_pop and count for yourself.

A wait that expires is success, not failure (M26.8). SubmitIoRing answers IORING_E_WAIT_TIMEOUT in that case, and its documented meaning is “All operations were submitted without error and the subsequent wait timed out” – so the submission half did everything it was asked to. Reporting that as an Err is the defect M21.6 fixed for crate::IoRing::pop_within; this site was missed by that sweep and kept it until M26.8. The distinction is not cosmetic: an Err here means the entries are still queued, and a caller who frees their buffers on seeing one would hand the kernel freed memory on the next submit.

§Errors

Returns any error from SubmitIoRing other than an expired wait. As Batch::submit, an error means the entries remain in the submission queue and their buffers must stay alive.

Trait Implementations§

Source§

impl<T, X, K> Drop for Batch<'_, T, X, K>

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§

§

impl<'ring, T = (), X = (), K = Open> !Sync for Batch<'ring, T, X, K>

§

impl<'ring, T = (), X = (), K = Open> !UnwindSafe for Batch<'ring, T, X, K>

§

impl<'ring, T, X, K> Freeze for Batch<'ring, T, X, K>
where &'ring mut IoRing<T, X, K>: Freeze,

§

impl<'ring, T, X, K> RefUnwindSafe for Batch<'ring, T, X, K>
where &'ring mut IoRing<T, X, K>: RefUnwindSafe,

§

impl<'ring, T, X, K> Send for Batch<'ring, T, X, K>
where &'ring mut IoRing<T, X, K>: Send,

§

impl<'ring, T, X, K> Unpin for Batch<'ring, T, X, K>
where &'ring mut IoRing<T, X, K>: Unpin,

§

impl<'ring, T, X, K> UnsafeUnpin for Batch<'ring, T, X, K>
where &'ring mut IoRing<T, X, K>: UnsafeUnpin,

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.