Expand description
A persistent, fsync-durable binary stack backed by a single file.
§Overview
BStack treats a file as a flat byte buffer that grows and shrinks from
the tail. Every mutating operation — push and
pop — calls a durable sync before returning, so the data
survives a process crash or an unclean system shutdown. Read-only
operations — peek and get — never modify
the file and on Unix can run concurrently with each other.
The crate has no external dependencies beyond libc on Unix and uses
no unsafe code beyond the required libc FFI calls.
§File format
Every file begins with a fixed 16-byte header:
┌────────────────────────┬──────────────┬──────────────┐
│ header (16 B) │ payload 0 │ payload 1 │ ...
│ magic[8] | clen[8 LE] │ │ │
└────────────────────────┴──────────────┴──────────────┘
^ ^ ^ ^
file offset 0 offset 16 16+n0 EOFmagic— the 8-byte sequenceBSTK\x00\x01\x00\x00(crate magic + version 0.1).openrejects any file whose first 8 bytes do not match.clen— little-endianu64recording the committed payload length. It is updated atomically with eachpushorpopand is used for crash recovery on the nextopen.
All user-visible offsets are logical (0-based from the start of the payload region, i.e. from file byte 16).
§Crash recovery
On open, the header’s committed length is compared against
the actual file size:
| Condition | Cause | Recovery |
|---|---|---|
file_size − 16 > clen | partial tail write (push crashed before header update) | truncate to 16 + clen |
file_size − 16 < clen | partial truncation (pop crashed before header update) | set clen = file_size − 16 |
After recovery a durable_sync ensures the repaired state is on stable
storage before any caller can observe or modify the file.
§Durability
| Operation | Syscall sequence |
|---|---|
push | lseek(END) → write(data) → lseek(8) → write(clen) → durable_sync |
pop | lseek → read → ftruncate → lseek(8) → write(clen) → durable_sync |
durable_sync on macOS issues fcntl(F_FULLFSYNC), which flushes the
drive’s hardware write cache. Plain fdatasync is not sufficient on macOS
because the kernel may acknowledge it before the drive controller has
committed the data. If F_FULLFSYNC is not supported by the device the
implementation falls back to sync_data (fdatasync).
durable_sync on other Unix calls sync_data (fdatasync), which is
sufficient on Linux and BSD.
§Multi-process safety
On Unix, open acquires an exclusive advisory flock
on the file (LOCK_EX | LOCK_NB). If another process already holds the
lock, open returns immediately with io::ErrorKind::WouldBlock rather
than blocking indefinitely. The lock is released automatically when the
BStack is dropped (the underlying file descriptor is closed).
Note:
flockis advisory and per-process. It prevents well-behaved concurrent opens across processes but does not protect against processes that bypass the lock or against raw writes to the file.
§Correct usage
bstack files must only be opened through this crate or a compatible
implementation that understands the file format, the header protocol, and
the locking semantics. Reading or writing the underlying file with raw
tools or syscalls while a BStack instance is live — or manually editing
the header fields — can silently corrupt the committed-length sentinel or
bypass the advisory lock.
The authors make no guarantees about the behaviour of this crate — including freedom from data loss or logical corruption — when the file has been accessed outside of this crate’s controlled interface.
§Thread safety
BStack wraps the file in a std::sync::RwLock.
| Operation | Lock (Unix) | Lock (non-Unix) |
|---|---|---|
push, pop | write | write |
peek, get | read | write |
len | read | read |
On Unix, peek and get use pread(2), which reads at an absolute file
offset without touching the file-position cursor. This allows multiple
concurrent peek/get/len calls to run in parallel while any ongoing
push or pop still serialises all readers via the write lock.
On non-Unix platforms a seek is required, so peek and get fall back to
the write lock and all reads serialise.
§Examples
use bstack::BStack;
let stack = BStack::open("log.bin")?;
// push returns the logical byte offset where the payload starts.
let off0 = stack.push(b"hello")?; // 0
let off1 = stack.push(b"world")?; // 5
assert_eq!(stack.len()?, 10);
// peek reads from a logical offset to the end without removing anything.
assert_eq!(stack.peek(off1)?, b"world");
// get reads an arbitrary half-open logical byte range.
assert_eq!(stack.get(3, 8)?, b"lowor");
// pop removes bytes from the tail and returns them.
assert_eq!(stack.pop(5)?, b"world");
assert_eq!(stack.len()?, 5);Structs§
- BStack
- A persistent, fsync-durable binary stack backed by a single file.