pub struct BStack { /* private fields */ }Expand description
A persistent, fsync-durable binary stack backed by a single file.
See the crate-level documentation for the file format, durability guarantees, crash recovery, multi-process safety, and thread-safety model.
Implementations§
Source§impl BStack
impl BStack
Sourcepub fn open(path: impl AsRef<Path>) -> Result<Self>
pub fn open(path: impl AsRef<Path>) -> Result<Self>
Open or create a stack file at path.
On a new file the 16-byte header is written and durably synced before returning.
On an existing file the header is validated and, if a previous crash left the file in an inconsistent state, the file is repaired and durably synced before returning (see Crash recovery in the crate docs).
On Unix an exclusive advisory flock is acquired; if another
process already holds the lock this function returns immediately with
io::ErrorKind::WouldBlock.
§Errors
io::ErrorKind::WouldBlock— another process holds the exclusive lock (Unix only).io::ErrorKind::InvalidData— the file exists but its header magic is wrong (not a bstack file, or created by an incompatible version), or the file is too short to contain a valid header.- Any
io::ErrorfromOpenOptions::open,read,write, ordurable_sync.
Sourcepub fn push(&self, data: impl AsRef<[u8]>) -> Result<u64>
pub fn push(&self, data: impl AsRef<[u8]>) -> Result<u64>
Append data to the end of the file.
Returns the logical byte offset at which data begins — i.e. the
payload size immediately before the write. An empty slice is valid; it
writes nothing and returns the current end offset.
§Atomicity
Either the full payload is written, the header committed-length is
updated, and the whole thing is durably synced, or the file is
left unchanged (best-effort rollback via ftruncate + header reset).
§Errors
Returns any io::Error from write_all, durable_sync, or the
fallback set_len.
Sourcepub fn extend(&self, n: u64) -> Result<u64>
pub fn extend(&self, n: u64) -> Result<u64>
Append n zero bytes to the end of the file.
Returns the logical byte offset at which the zeros begin — i.e. the
payload size immediately before the write. n = 0 is valid; it writes
nothing and returns the current end offset.
§Atomicity
Either the file is extended, the header committed-length is updated,
and the whole thing is durably synced, or the file is left unchanged
(best-effort rollback via ftruncate + header reset).
§Errors
Returns any io::Error from set_len, durable_sync, or the
fallback set_len.
Sourcepub fn pop(&self, n: u64) -> Result<Vec<u8>>
pub fn pop(&self, n: u64) -> Result<Vec<u8>>
Remove and return the last n bytes of the file.
n = 0 is valid: no bytes are removed and an empty Vec is returned.
n may span across multiple previous push boundaries.
§Atomicity
The bytes are read before the file is truncated. The committed-length in the header is updated and durably synced after the truncation.
§Errors
Returns io::ErrorKind::InvalidInput if n exceeds the current
payload size. Also propagates any I/O error from read_exact,
set_len, write_all, or durable_sync.
Sourcepub fn peek(&self, offset: u64) -> Result<Vec<u8>>
pub fn peek(&self, offset: u64) -> Result<Vec<u8>>
Return a copy of every payload byte from offset to the end of the
file.
offset is a logical offset (as returned by push).
offset == len() is valid and returns an empty Vec. The file is not
modified.
§Concurrency
On Unix and Windows this uses a cursor-safe positional read (pread(2)
on Unix; ReadFile+OVERLAPPED on Windows), so the method takes only
the read lock, allowing multiple concurrent peek and get calls
to run in parallel.
On other platforms a seek is required; the method falls back to the write lock and concurrent reads serialise.
§Errors
Returns io::ErrorKind::InvalidInput if offset exceeds the current
payload size.
Sourcepub fn get(&self, start: u64, end: u64) -> Result<Vec<u8>>
pub fn get(&self, start: u64, end: u64) -> Result<Vec<u8>>
Return a copy of the bytes in the half-open logical range [start, end).
start == end is valid and returns an empty Vec. The file is not
modified.
§Concurrency
Same as peek: on Unix and Windows the read lock is
taken and concurrent get/peek/len calls may run in parallel. On
other platforms the write lock is taken and reads serialise.
§Errors
Returns io::ErrorKind::InvalidInput if end < start or if end
exceeds the current payload size.
Sourcepub fn peek_into(&self, offset: u64, buf: &mut [u8]) -> Result<()>
pub fn peek_into(&self, offset: u64, buf: &mut [u8]) -> Result<()>
Fill buf with bytes from logical offset to offset + buf.len().
Reads exactly buf.len() bytes from offset into the caller-supplied
buffer. An empty buffer is a valid no-op. The file is not modified.
Use this instead of peek when the destination buffer is
already allocated and you want to avoid the extra heap allocation.
§Concurrency
Same as peek: on Unix and Windows only the read lock is
taken; on other platforms the write lock serialises all reads.
§Errors
Returns io::ErrorKind::InvalidInput if offset + buf.len() overflows
u64 or exceeds the current payload size.
Sourcepub fn get_into(&self, start: u64, buf: &mut [u8]) -> Result<()>
pub fn get_into(&self, start: u64, buf: &mut [u8]) -> Result<()>
Fill buf with bytes from the half-open logical range
[start, start + buf.len()).
An empty buffer is a valid no-op. The file is not modified.
Use this instead of get when the destination buffer is
already allocated and you want to avoid the extra heap allocation.
§Concurrency
Same as get: on Unix and Windows only the read lock is
taken; on other platforms the write lock serialises all reads.
§Errors
Returns io::ErrorKind::InvalidInput if start + buf.len() overflows
u64 or exceeds the current payload size.
Sourcepub fn pop_into(&self, buf: &mut [u8]) -> Result<()>
pub fn pop_into(&self, buf: &mut [u8]) -> Result<()>
Remove the last buf.len() bytes from the file and write them into buf.
An empty buffer is a valid no-op: no bytes are removed.
Use this instead of pop when the destination buffer is
already allocated and you want to avoid the extra heap allocation.
§Atomicity
Same guarantees as pop.
§Errors
Returns io::ErrorKind::InvalidInput if buf.len() exceeds the
current payload size. Also propagates any I/O error from read_exact,
set_len, write_all, or durable_sync.
Sourcepub fn discard(&self, n: u64) -> Result<()>
pub fn discard(&self, n: u64) -> Result<()>
Remove (discard) the last n bytes from the file without returning them.
Equivalent to pop but avoids allocating a buffer for the
removed bytes. n = 0 is valid and is a no-op.
§Atomicity
Same guarantees as pop.
§Errors
Returns io::ErrorKind::InvalidInput if n exceeds the current
payload size. Also propagates any I/O error from set_len,
write_all, or durable_sync.
Sourcepub fn set(&self, offset: u64, data: impl AsRef<[u8]>) -> Result<()>
pub fn set(&self, offset: u64, data: impl AsRef<[u8]>) -> Result<()>
Overwrite data bytes in place starting at logical offset.
The file size is never changed: if offset + data.len() would exceed
the current payload size the call is rejected. An empty slice is a
valid no-op.
§Feature flag
Only available when the set Cargo feature is enabled.
§Durability
Equivalent to push/pop: the overwritten bytes are durably synced
before the call returns.
§Errors
Returns io::ErrorKind::InvalidInput if offset + data.len()
exceeds the current payload size, or if the addition overflows u64.
Propagates any I/O error from write_all or durable_sync.
Sourcepub fn zero(&self, offset: u64, n: u64) -> Result<()>
pub fn zero(&self, offset: u64, n: u64) -> Result<()>
Overwrite n bytes with zeros in place starting at logical offset.
The file size is never changed: if offset + n would exceed
the current payload size the call is rejected. n = 0 is a
valid no-op.
§Feature flag
Only available when the set Cargo feature is enabled.
§Durability
Equivalent to push/pop: the overwritten bytes are durably synced
before the call returns.
§Errors
Returns io::ErrorKind::InvalidInput if offset + n
exceeds the current payload size, or if the addition overflows u64.
Propagates any I/O error from write_all or durable_sync.
Source§impl BStack
impl BStack
Sourcepub fn atrunc(&self, n: u64, buf: impl AsRef<[u8]>) -> Result<()>
pub fn atrunc(&self, n: u64, buf: impl AsRef<[u8]>) -> Result<()>
Cut n bytes off the tail then append buf as a single atomic operation.
The operation ordering is chosen based on the net size change to maximise crash-recovery safety (see Durability in the crate docs):
-
Net extension (
buf.len() > n): the file is extended first,bufis written into the freed tail region plus the new space, then adurable_synccommits the data before the header committed-length is updated. On crash before the header update, recovery truncates back to the original committed length — a clean rollback. -
Net truncation or same size (
buf.len() ≤ n):bufis written into the tail first, then the file is truncated, thendurable_synccommits the result before the header is updated. On crash after truncation, recovery sets the committed length to the (smaller) file size, committing the final state.
n = 0 with an empty buf is a valid no-op.
§Feature flag
Only available when the atomic Cargo feature is enabled.
§Errors
Returns io::ErrorKind::InvalidInput if n exceeds the current
payload size. Propagates any I/O error from set_len, write_all,
or durable_sync.
Sourcepub fn splice(&self, n: u64, buf: impl AsRef<[u8]>) -> Result<Vec<u8>>
pub fn splice(&self, n: u64, buf: impl AsRef<[u8]>) -> Result<Vec<u8>>
Pop n bytes off the tail then append buf, returning the removed bytes.
The bytes are read before any mutation, so they are always available in
the returned Vec even if the subsequent write fails. The same
ordering strategy as atrunc is used.
n = 0 with an empty buf is a valid no-op and returns an empty Vec.
§Feature flag
Only available when the atomic Cargo feature is enabled.
§Errors
Returns io::ErrorKind::InvalidInput if n exceeds the current
payload size. Propagates any I/O error from read_exact, set_len,
write_all, or durable_sync.
Sourcepub fn splice_into(&self, old: &mut [u8], new: impl AsRef<[u8]>) -> Result<()>
pub fn splice_into(&self, old: &mut [u8], new: impl AsRef<[u8]>) -> Result<()>
Pop old.len() bytes off the tail into old, then append new.
Buffer-reuse counterpart of splice: avoids allocating
a Vec for the removed bytes by writing them into the caller-supplied
old slice. The same ordering strategy as atrunc is
used for the write/truncation side.
An empty old with an empty new is a valid no-op.
§Feature flag
Only available when the atomic Cargo feature is enabled.
§Errors
Returns io::ErrorKind::InvalidInput if old.len() exceeds the
current payload size. Propagates any I/O error from read_exact,
set_len, write_all, or durable_sync.
Sourcepub fn try_extend(&self, s: u64, buf: impl AsRef<[u8]>) -> Result<bool>
pub fn try_extend(&self, s: u64, buf: impl AsRef<[u8]>) -> Result<bool>
Append buf only if the current logical payload size equals s.
Returns Ok(true) if the size matched and buf was appended (or buf
is empty and no I/O was needed). Returns Ok(false) without modifying
the file if the size does not match.
§Feature flag
Only available when the atomic Cargo feature is enabled.
§Errors
Propagates any I/O error from write_all, write_committed_len, or
durable_sync.
Sourcepub fn try_discard(&self, s: u64, n: u64) -> Result<bool>
pub fn try_discard(&self, s: u64, n: u64) -> Result<bool>
Discard n bytes only if the current logical payload size equals s.
Returns Ok(true) if the size matched and n bytes were removed (or
n = 0 and the size check passed without I/O). Returns Ok(false)
without modifying the file if the size does not match.
When n = 0 only the read lock is taken (no file mutation occurs).
§Feature flag
Only available when the atomic Cargo feature is enabled.
§Errors
Returns io::ErrorKind::InvalidInput if n exceeds the current
payload size. Propagates any I/O error from set_len,
write_committed_len, or durable_sync.
Sourcepub fn replace<F>(&self, n: u64, f: F) -> Result<()>
pub fn replace<F>(&self, n: u64, f: F) -> Result<()>
Pop n bytes off the tail, pass them read-only to a callback that
returns the new tail bytes, then write the new tail.
The read, callback invocation, and write all happen under the same write
lock, so no other thread can observe the state between the pop and the
push. The callback may return a Vec<u8> of any length — the file
will grow or shrink accordingly using the same crash-safe ordering
strategy as atrunc.
n = 0 is valid: the callback receives an empty slice and whatever it
returns is appended.
§Feature flag
Only available when the atomic Cargo feature is enabled.
§Errors
Returns io::ErrorKind::InvalidInput if n exceeds the current
payload size. Propagates any I/O error from read_exact, set_len,
write_all, or durable_sync.
Source§impl BStack
impl BStack
Sourcepub fn swap(&self, offset: u64, buf: impl AsRef<[u8]>) -> Result<Vec<u8>>
pub fn swap(&self, offset: u64, buf: impl AsRef<[u8]>) -> Result<Vec<u8>>
Atomically read buf.len() bytes at offset and overwrite them with
buf, returning the old contents.
Both the read and the write happen under the same write lock, so no other thread can observe either the pre-swap or mid-swap state. The file size is never changed.
An empty buf is a valid no-op and returns an empty Vec.
§Feature flags
Only available when both the set and atomic Cargo features are
enabled.
§Errors
Returns io::ErrorKind::InvalidInput if offset + buf.len()
overflows u64 or exceeds the current payload size. Propagates any
I/O error from read_exact, write_all, or durable_sync.
Sourcepub fn swap_into(&self, offset: u64, buf: &mut [u8]) -> Result<()>
pub fn swap_into(&self, offset: u64, buf: &mut [u8]) -> Result<()>
Atomically read buf.len() bytes at offset into buf while writing
the original contents of buf into that position.
On return, buf contains the bytes that were previously at offset,
and the file contains what buf held on entry. Buffer-reuse
counterpart of swap.
An empty buf is a valid no-op.
§Feature flags
Only available when both the set and atomic Cargo features are
enabled.
§Errors
Returns io::ErrorKind::InvalidInput if offset + buf.len()
overflows u64 or exceeds the current payload size. Propagates any
I/O error from read_exact, write_all, or durable_sync.
Sourcepub fn cas(
&self,
offset: u64,
old: impl AsRef<[u8]>,
new: impl AsRef<[u8]>,
) -> Result<bool>
pub fn cas( &self, offset: u64, old: impl AsRef<[u8]>, new: impl AsRef<[u8]>, ) -> Result<bool>
Compare-and-exchange: read old.len() bytes at offset and, if they
equal old, overwrite them with new.
Returns Ok(true) if the comparison succeeded and the exchange was
performed. Returns Ok(false) without modifying the file if
old.len() != new.len() or if the current bytes do not match old.
Both the compare and the exchange happen under the same write lock.
§Feature flags
Only available when both the set and atomic Cargo features are
enabled.
§Errors
Returns io::ErrorKind::InvalidInput if offset + old.len()
overflows u64 or exceeds the current payload size. Propagates any
I/O error from read_exact, write_all, or durable_sync.
Sourcepub fn process<F>(&self, start: u64, end: u64, f: F) -> Result<()>
pub fn process<F>(&self, start: u64, end: u64, f: F) -> Result<()>
Read bytes in the half-open logical range [start, end), pass them to
a callback that may mutate them in place, then write the modified bytes
back.
The read, callback invocation, and write all happen under the same write lock, so no other thread can observe an intermediate state. The file size is never changed.
start == end is a valid no-op: f is called with an empty slice and
no I/O is performed beyond the initial size check.
§Feature flags
Only available when both the set and atomic Cargo features are
enabled.
§Errors
Returns io::ErrorKind::InvalidInput if end < start or if end
exceeds the current payload size. Propagates any I/O error from
read_exact, write_all, or durable_sync.
Source§impl BStack
impl BStack
Sourcepub fn len(&self) -> Result<u64>
pub fn len(&self) -> Result<u64>
Return the current logical payload size in bytes (excludes the 16-byte header).
Takes the read lock, so it can run concurrently with other len calls
but blocks while any write-lock operation is in progress. The returned
value always reflects a clean operation boundary.
§Errors
Propagates any io::Error from File::metadata.
Source§impl BStack
impl BStack
Sourcepub fn reader(&self) -> BStackReader<'_> ⓘ
pub fn reader(&self) -> BStackReader<'_> ⓘ
Create a BStackReader positioned at the start of the payload.
Sourcepub fn reader_at(&self, offset: u64) -> BStackReader<'_> ⓘ
pub fn reader_at(&self, offset: u64) -> BStackReader<'_> ⓘ
Create a BStackReader positioned at offset bytes into the payload.
Seeking past the current end is allowed; read will
return Ok(0) until new data is pushed past that point.
Trait Implementations§
Source§impl<'a> From<&'a BStack> for BStackReader<'a>
impl<'a> From<&'a BStack> for BStackReader<'a>
Source§impl From<BStack> for LinearBStackAllocator
impl From<BStack> for LinearBStackAllocator
Source§impl<'a> From<BStackReader<'a>> for &'a BStack
impl<'a> From<BStackReader<'a>> for &'a BStack
Source§fn from(val: BStackReader<'a>) -> Self
fn from(val: BStackReader<'a>) -> Self
Source§impl From<LinearBStackAllocator> for BStack
impl From<LinearBStackAllocator> for BStack
Source§fn from(alloc: LinearBStackAllocator) -> Self
fn from(alloc: LinearBStackAllocator) -> Self
Source§impl Hash for BStack
Hashes the instance address, consistent with the pointer-identity PartialEq.
impl Hash for BStack
Hashes the instance address, consistent with the pointer-identity PartialEq.
Source§impl PartialEq for BStack
Two BStack instances are equal iff they are the same instance in memory.
impl PartialEq for BStack
Two BStack instances are equal iff they are the same instance in memory.
Because BStack::open acquires an exclusive advisory lock, no two
BStack values within one process can refer to the same file at the same
time. Pointer identity is therefore the only meaningful equality: a stack
is equal to itself and to nothing else.
Source§impl Write for &BStack
Shared-reference counterpart of impl Write for BStack.
impl Write for &BStack
Shared-reference counterpart of impl Write for BStack.
Because push takes &self (interior mutability via
RwLock), the Write implementation is also available on &BStack,
mirroring the standard library’s impl Write for &File.
Source§fn write(&mut self, buf: &[u8]) -> Result<usize>
fn write(&mut self, buf: &[u8]) -> Result<usize>
Source§fn flush(&mut self) -> Result<()>
fn flush(&mut self) -> Result<()>
Source§fn is_write_vectored(&self) -> bool
fn is_write_vectored(&self) -> bool
can_vector)1.0.0 · Source§fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>
fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>
Source§fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>
write_all_vectored)Source§impl Write for BStack
Appends bytes to the stack.
impl Write for BStack
Appends bytes to the stack.
Each call to write is equivalent to push:
all bytes are written atomically and durably synced before returning.
Calling write_all or chaining multiple write calls therefore issues
one durable_sync per call — callers that need to batch many small writes
without per-write syncs should accumulate data and call push directly.
flush is a no-op because every write is already
durable.
Source§fn write(&mut self, buf: &[u8]) -> Result<usize>
fn write(&mut self, buf: &[u8]) -> Result<usize>
Source§fn flush(&mut self) -> Result<()>
fn flush(&mut self) -> Result<()>
Source§fn is_write_vectored(&self) -> bool
fn is_write_vectored(&self) -> bool
can_vector)1.0.0 · Source§fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>
fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>
Source§fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<(), Error>
write_all_vectored)