Skip to main content

Crate bstack

Crate bstack 

Source
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, pop, and (with the set feature) set — 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          EOF
  • magic — 8 bytes: BSTK + major(1 B) + minor(1 B) + patch(1 B) + reserved(1 B). This version writes BSTK\x00\x01\x01\x00 (0.1.1). open accepts any file whose first 6 bytes match BSTK\x00\x01 (any 0.1.x) and rejects anything with a different major or minor.
  • clen — little-endian u64 recording the committed payload length. It is updated atomically with each push or pop and is used for crash recovery on the next open.

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:

ConditionCauseRecovery
file_size − 16 > clenpartial tail write (push crashed before header update)truncate to 16 + clen
file_size − 16 < clenpartial 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

OperationSyscall sequence
pushlseek(END)write(data)lseek(8)write(clen)durable_sync
poplseekreadftruncatelseek(8)write(clen)durable_sync
set (feature)lseek(offset)write(data)durable_sync
peek, getpread(2) on Unix; lseekread elsewhere (no sync — read-only)

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: flock is 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.

OperationLock (Unix)Lock (non-Unix)
push, popwritewrite
set (feature)writewrite
peek, getreadwrite
lenreadread

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.

§Feature flags

FeatureDescription
setEnables [BStack::set] — in-place overwrite of existing payload bytes without changing the file size.

Enable with:

[dependencies]
bstack = { version = "0.1", features = ["set"] }

§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.