pub trait FailureStore {
type Factory: StreamFactory<Stream: StreamMemory>;
// Required methods
fn split(&mut self) -> (&mut StreamPool<Self::Factory>, &mut Failures);
fn parts(&self) -> (&StreamPool<Self::Factory>, &Failures);
// Provided methods
fn ensure_written<'a>(
&self,
handles: impl Iterator<Item = &'a BufferBinding>,
) -> Result<(), ServerError> { ... }
fn read_failure<'a>(
&self,
reads: impl Iterator<Item = &'a BufferBinding>,
) -> Option<ReadFailure> { ... }
fn taint<'a>(
&mut self,
error: ServerError,
written: impl Iterator<Item = &'a BufferBinding>,
) { ... }
fn written<'a>(&mut self, written: impl Iterator<Item = &'a BufferBinding>) { ... }
fn propagate(
&mut self,
found: &ReadFailure,
kernel: KernelId,
written: Vec<BufferBinding>,
) { ... }
fn write_set(&mut self) -> Vec<BufferBinding> { ... }
fn enter_write(&mut self, written: &[BufferBinding]) -> Option<FailureId> { ... }
fn exit_write(
&mut self,
provisional: Option<FailureId>,
written: Vec<BufferBinding>,
error: Option<&ServerError>,
) { ... }
}Expand description
A multi-stream driver that owns a device’s Failures.
Implementing split and parts buys the
whole taint surface below, and the write scope’s hooks with it. Whether a
buffer can be trusted lives on its allocation, so every operation here is
the same two steps: resolve each binding to the stream that allocated it,
and tell that stream’s memory what happened.
Required Associated Types§
Sourcetype Factory: StreamFactory<Stream: StreamMemory>
type Factory: StreamFactory<Stream: StreamMemory>
The factory the driver’s StreamPool was built from. Its streams
expose the memory the taint is recorded on.
Required Methods§
Sourcefn split(&mut self) -> (&mut StreamPool<Self::Factory>, &mut Failures)
fn split(&mut self) -> (&mut StreamPool<Self::Factory>, &mut Failures)
The pool and the store, split-borrowed: nearly every operation here reaches the allocations through the pool while mutating the store.
Provided Methods§
Sourcefn ensure_written<'a>(
&self,
handles: impl Iterator<Item = &'a BufferBinding>,
) -> Result<(), ServerError>
fn ensure_written<'a>( &self, handles: impl Iterator<Item = &'a BufferBinding>, ) -> Result<(), ServerError>
Fails when the buffers handles name carry a failure, with the errors
of the work that was supposed to write them.
A read is only as good as the work that wrote the buffer: a launch that failed never wrote it, so copying its bytes out hands back whatever was in memory before. Whether that happened is a field on the allocation, so the question is answered by the slice each binding resolves to — a lookup the read was going to do anyway — and by nobody’s queue.
§Errors
ServerError::Several naming every failure one of these buffers
carries, each failure once however many buffers carry it, and every
buffer that was never allocated. The caller has nothing to retry — the
bytes are gone — so the error is the answer to the read, not a hint to
try again.
Sourcefn read_failure<'a>(
&self,
reads: impl Iterator<Item = &'a BufferBinding>,
) -> Option<ReadFailure>
fn read_failure<'a>( &self, reads: impl Iterator<Item = &'a BufferBinding>, ) -> Option<ReadFailure>
The failure claiming bytes any of reads names, with its error — the
check a launch makes before it runs.
A launch whose input cannot be trusted does not run: a buffer holding garbage can be read as a dynamic cube count or as indices in a gather, so running would risk dispatching an absurd grid or scattering into memory that carried no failure at all. Skipping costs the same lookup, because the inputs have to be read either way to decide anything.
Sourcefn taint<'a>(
&mut self,
error: ServerError,
written: impl Iterator<Item = &'a BufferBinding>,
)
fn taint<'a>( &mut self, error: ServerError, written: impl Iterator<Item = &'a BufferBinding>, )
Taint every allocation in written with error: the work that was
going to write those buffers did not run, so a read of any of them
fails on this failure until something writes them again.
Each binding is resolved to the manager of the stream it was created on, which may not be the stream that failed — that is the point: the fact lands on the memory, wherever it lives.
Sourcefn written<'a>(&mut self, written: impl Iterator<Item = &'a BufferBinding>)
fn written<'a>(&mut self, written: impl Iterator<Item = &'a BufferBinding>)
Release the failure on every allocation in written: work that writes
them has been enqueued, so a read of one is no longer reading bytes
nothing wrote.
Sourcefn propagate(
&mut self,
found: &ReadFailure,
kernel: KernelId,
written: Vec<BufferBinding>,
)
fn propagate( &mut self, found: &ReadFailure, kernel: KernelId, written: Vec<BufferBinding>, )
A skipped launch’s outputs take the failure that stopped it: nothing wrote them, exactly as if the launch had failed, and the claim names the root cause rather than minting a new one. The skip is recorded on the failure, so a read of anything downstream can name the path back to the root.
Takes the write set by value and hands it back to the pool, the same
contract exit_write has, because a skip is the
other way a scope ends: a loop carrying a tainted buffer forward skips
on every iteration — the most frequent event in this whole design — and
a set the skip path dropped would allocate a fresh one every time.
Sourcefn write_set(&mut self) -> Vec<BufferBinding>
fn write_set(&mut self) -> Vec<BufferBinding>
An empty write set, pooled here so a launch allocates nothing for it.
exit_write hands it back.
Sourcefn enter_write(&mut self, written: &[BufferBinding]) -> Option<FailureId>
fn enter_write(&mut self, written: &[BufferBinding]) -> Option<FailureId>
Enter a write scope over written: taint every buffer the work is
going to write with a provisional failure, minted here because the real
one does not exist yet.
The default this sets is tainted unless proven written — the opposite
of clearing on success and hoping every failure path remembered to
taint. A body that returns early, or panics before
exit_write runs, leaves the write set carrying
this failure, so a read of one of its buffers fails loudly instead of
returning bytes nothing wrote.
An empty write set — a dry run, a launch writing nothing — claims nothing and mints nothing.
Sourcefn exit_write(
&mut self,
provisional: Option<FailureId>,
written: Vec<BufferBinding>,
error: Option<&ServerError>,
)
fn exit_write( &mut self, provisional: Option<FailureId>, written: Vec<BufferBinding>, error: Option<&ServerError>, )
Settle the scope entered over written: release the provisional
failure when the work was enqueued, and swap the real error in for it
when the work was not. The taint is the whole answer — a read of one
of these buffers fails on it, whoever asks — and the error is logged
here, the backstop for the failure nobody ever reads. The staged
vector goes back to the pool either way.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".