pub struct Completion { /* private fields */ }Expand description
One popped completion (M3.7): the operation’s identity, from
IORING_CQE::UserData, and its result.
Implementations§
Source§impl Completion
impl Completion
Sourcepub fn user_data(&self) -> usize
pub fn user_data(&self) -> usize
The UserData identity this completion reports.
Not a way to reach what the operation was holding. The pop that
produced this completion already returned that, and it is the only
call that can – see IoRing::try_pop. This is the integer for
correlation and for naming a cancel target, which is the same thing
crate::OperationId is and for the same reason.
It used to say “match it against a held Token”, and that instruction
was D-55’s evidence that the crate handed a
caller two halves and connected them with nothing.
Sourcepub fn result(&self) -> Result<usize>
pub fn result(&self) -> Result<usize>
This op’s result: the transferred byte count (read/write) or other
op-specific value in IORING_CQE::Information, once ResultCode
says success.
§The count may be short, and the remainder is yours
A successful read or write may report fewer bytes than were
requested, including zero (RS-P-8 in
RESPONSE-SPACE.md). WriteFile documents this
for non-blocking byte-mode pipes; sockets report a short send when the
transmit buffer is full, and reads are short at end of file. This crate
takes a handle and does not constrain what kind it is, so a consumer
must compare this count against the length it submitted rather than
assume they are equal.
Nothing here reissues the remainder. Whether to submit another operation for it, how many times, and when to give up are the caller’s (D-67), and a consumer that loops must tolerate a completion that makes no progress.
A consumer that has narrowed its own handle type can rely on more – for an ordinary file on a local volume a successful completion is expected to carry the full length, and a full volume is an error rather than a short success. That is a guarantee such a consumer earns by constraining the handle, and it belongs in its contract rather than being assumed from this one.
§Errors
Returns the wrapped crate::IoRingError if ResultCode is a
failure – for example ERROR_NOT_FOUND when a cancel target was not
actually outstanding.
Sourcepub fn with_injected_failure(self, failure: InjectedFailure) -> Self
pub fn with_injected_failure(self, failure: InjectedFailure) -> Self
Report failure from Completion::result instead of what this
operation actually returned (M16.3).
§What this is for
Failure paths that are otherwise unreachable in a test. Most of what a consumer must handle – a write that fails partway, a flush the device rejects, a registration the kernel refuses – cannot be provoked on demand from a healthy machine, so that code is typically written once and never executed again. This crate shipped a defect of exactly that shape: a claim path that returned early on a failed write and leaked its registered-buffer slot permanently, on a branch no test had ever taken.
§Why this is sound, where fabricating a completion would not be
The pop’s safety argument is that a Completion for some UserData
existing at all proves the kernel has finished with that operation,
and therefore that handing its buffer back is sound.
This method consumes a completion the ring genuinely popped and returns
one carrying the same UserData and the same ring identity. The
operation really did finish; the only thing that changes is what
Completion::result says about it. So the argument above is
untouched, and claiming against the result is exactly as sound as
claiming against the original.
Completion::synthetic is the opposite, and is why it is not
reachable from here. A fabricated completion can name an operation that
is still in flight, and reclaiming a payload against one hands a
buffer back to the caller while the kernel is still writing through it – the
precise use-after-free this crate exists to prevent. That is a
test-only, crate-only tool and stays one.
§What it cannot do
Only failure can be injected, never success. Turning a real failure
into an apparent success would let a test conceal a genuine defect,
which is the wrong affordance for a tool whose whole purpose is
exercising error handling. That is enforced rather than merely stated:
a non-failing HRESULT panics.
§Panics
If failure does not resolve to a failing HRESULT. Only
InjectedFailure::Hresult can express that at all.
§Prefer a real failure when one is reachable
A genuine kernel error – writing through a read-only handle, cancelling something that is not outstanding – tests the real path and confirms the crate’s own error translation. Reach for this only where no such route exists.
§The one place injection is not inert: registration completions
The soundness argument above is about the pop, where a failed
completion changes nothing the caller does with memory: the buffer
comes back either way. A registration claim is different.
crate::PendingBufferRegistration::claim_if treats a failed
completion as proof the kernel did not retain the addresses, and so
drops the buffers. Inject a failure there and it frees memory the
kernel genuinely does hold registered – leaving the ring with a
registration pointing at freed pages.
That is inert only while nothing uses it. A test doing this must not push any registered-buffer operation on that ring afterwards, and should tear the ring down immediately.
This is a limitation of what a failed registration means, not
something the seam can check: a Completion does not carry which
operation produced it, so this cannot be refused at the call. Prefer a
genuine failure for registration paths wherever one can be provoked.
§Example
// ONE pop. The completion being injected into has already been popped,
// and that same pop is what retired the inventory entry and handed the
// payload back -- there is no second pop that could return it again.
let (completion, payload) = ring
.pop_within(WAIT)
.expect("pop_within")
.expect("a completion arrives within the wait");
// The payload comes back whatever the result says: the operation did
// complete, and a failed completion is still the proof that frees the
// buffer.
let completion = completion.with_injected_failure(
InjectedFailure::Win32(ERROR_ACCESS_DENIED),
);
assert!(completion.result().is_err());Sourcepub fn with_injected_transfer(self, transferred: usize) -> Self
pub fn with_injected_transfer(self, transferred: usize) -> Self
Report a successful transfer of transferred bytes instead of what
this operation actually reported (M26.11).
§What this is for, and why the failure seam cannot do it
Completion::with_injected_failure models an operation that failed.
RS-P-8 describes something different and stranger: an operation that
succeeded while moving fewer bytes than were asked for. Windows
documents that for non-blocking byte-mode pipes and it happens on
sockets, but an ordinary file on a local volume does not do it – so a
consumer’s handling of a short count is written once against the
documentation and then never executed again, which is exactly the class
of path the failure seam was introduced for.
A consumer that narrows its handle type may legitimately require
complete transfers. This seam is how such a consumer tests that its
requirement is enforced rather than merely stated – epoch_log’s
appender uses it for precisely that.
§Why this one is inert
The result code is left successful and only the byte count moves, so
the pop behaves exactly as it would for the real completion: the buffer
comes back, and no path keys memory ownership off the transferred
count. That makes this seam free of the
registration hazard documented on
Completion::with_injected_failure, which arises only because a
failed registration is taken as proof the kernel retained nothing.
§Example
// A handle whose successful writes are complete is a requirement this
// consumer states; here we hand it one that violates the requirement.
let completion = completion.with_injected_transfer(RECORD_STRIDE - 1);
assert_eq!(completion.result().expect("still a success"), RECORD_STRIDE - 1);