Skip to main content

Crate rblock

Crate rblock 

Source
Expand description

§rblock

A small lock_api-compatible read-biased RwLock for sharded, read-heavy cache workloads.

This crate is intentionally narrow. It is not a general replacement for parking_lot::RwLock; it is a policy choice for very short cache-shard critical sections where read throughput matters more than bounded writer progress.

§What It Optimizes

The reader fast path only checks for an active writer. Waiting writers do not close a reader gate, so new readers can keep entering while a writer is queued. That avoids reader convoys in read-heavy mixed workloads.

The tradeoff is fairness. A constant stream of readers can delay a writer much longer than a fair lock would. Use this when that tradeoff is explicit and measured.

§When To Use It

Workload or system shapeRecommendation
Read-heavy sharded cache, very short read/write critical sectionsUse rblock::RwLock
Mostly reads with occasional small writes, latency target is read-side p99/p999Use rblock::RwLock
Write-heavy, skewed, or writer-tail-sensitive workloadPrefer parking_lot::RwLock or a fair policy
Long critical sections, IO while locked, blocking work while lockedDo not use this lock
General shared hash map where you want a ready-made concurrent mapConsider DashMap
Need bounded writer progress as a correctness or SLO propertyUse parking_lot::RwLock

§API

use rblock::RwLock;

let lock = RwLock::new(0usize);

{
    let value = lock.read();
    assert_eq!(*value, 0);
}

{
    let mut value = lock.write();
    *value += 1;
}

The exported guard types are:

  • RwLock<T>
  • RwLockReadGuard<'_, T>
  • RwLockWriteGuard<'_, T>

§Benchmarks

This crate includes lock-only benchmarks. Run them directly from the crate root:

cargo bench --bench rwlock_load -- \
  --duration-ms 500 \
  --warmup-ms 150 \
  --threads 1,2,4,8,16 \
  --shards 1,16,64 \
  --mixes 100-0,95-5,80-20,50-50,20-80,0-100 \
  --latency-sample-rate 1024

cargo bench --bench writer_progress -- \
  --duration-ms 500 \
  --warmup-ms 150 \
  --readers 1,2,4,8,16

For Linux runs, use the crate-local harness. It syncs only this crate into a temporary workspace on the server:

./scripts/run-linux-bench.sh

BENCH=writer_progress \
./scripts/run-linux-bench.sh \
  --duration-ms 500 \
  --warmup-ms 150 \
  --readers 1,2,4,8,16

For application-level A/B testing, compare your sharded data structure with this lock against the same data structure using a fair lock. Record both throughput and latency, including p50, p99, p999, read p999, and write p999. That matters: this lock can improve ops/sec and read tails while making writer tails worse under continuous readers.

§What The Load Benchmark Measures

rwlock_load is a lock-only benchmark. It creates an array of cache-padded lock slots and runs the same workload against rblock::RwLock<usize> and parking_lot::RwLock<usize>. Each operation chooses a shard uniformly at random, then either:

  • takes a read lock and reads the usize, or
  • takes a write lock, increments the usize, and releases the lock.

There is no hash table, key comparison, allocation, eviction, TTL check, IO, or application data structure in this benchmark. The point is to isolate lock policy under controlled contention.

The shards setting controls how concentrated that contention is:

  • shards=1 is one hot lock. Every thread contends for the same lock, which stresses the read-biased policy most directly.
  • shards=16 spreads operations uniformly over 16 independent locks. With 16 worker threads, the average contention per lock is much lower, so lock policy matters less and scheduler/cache effects matter more.
  • shards=64 spreads the same threads over many more locks. This approximates a well-striped cache where most operations do not collide on the same shard.

ops/sec is total completed lock operations per second across all worker threads. The latency columns are sampled end-to-end operation latency for the synthetic operation: shard choice, lock acquisition, the tiny critical section, and unlock. They combine reads and writes according to the selected mix; they are not separate read-latency and write-latency histograms.

§Representative Results

These are representative Linux results from May 14, 2026. They are workload-specific; rerun the harness for your hardware and key distribution before making a production decision.

§Lock-Only Load Benchmark

Command:

./scripts/run-linux-bench.sh \
  --duration-ms 2000 \
  --warmup-ms 500 \
  --threads 1,2,4,8,16 \
  --shards 1,16,64 \
  --mixes 80-20,50-50,20-80 \
  --latency-sample-rate 512

16 threads, one hot shard:

Mixrblock ops/secparking_lot ops/secrblock p999parking_lot p999
80/2022.40M12.92M58.0us69.8us
50/5020.78M9.36M54.4us77.3us
20/8024.26M7.73M58.0us88.3us

16 threads, 16 shards:

Mixrblock ops/secparking_lot ops/secrblock p999parking_lot p999
80/2087.47M94.19M1.4us1.9us
50/50102.64M103.54M1.6us2.0us
20/80120.76M119.56M1.5us1.9us

16 threads, 64 shards:

Mixrblock ops/secparking_lot ops/secrblock p999parking_lot p999
80/20204.83M195.43M740ns670ns
50/50211.84M209.63M640ns640ns
20/80230.00M232.46M610ns590ns

The single-shard case shows where the read-biased policy buys substantial aggregate throughput under contention. Once contention is spread over many shards, the result is much closer: rblock is competitive, but it is not a universal throughput win over parking_lot.

§Writer Progress Warning

Lock-only writer-progress harness, 16 reader threads in a tight loop, one writer thread repeatedly trying to acquire the write lock, zero synthetic work.

This is not a fixed offered-rate write workload. The writer thread runs as fast as the lock lets it, so writer acq/s is a progress signal under reader pressure. The latency columns are the main reason this benchmark exists.

Command:

BENCH=writer_progress \
./scripts/run-linux-bench.sh \
  --duration-ms 2000 \
  --warmup-ms 500 \
  --readers 1,2,4,8,16
Readersrblock writer acq/sparking_lot writer acq/srblock p999parking_lot p999
11.10M5.41M12.1us570ns
2220.0k4.20M53.9us1.4us
4125.9k2.36M65.0us5.1us
810.3k1.41M1.4ms12.3us
169658.6k230.7ms46.6us

This is the sharp edge. If writer progress or write p999 is the primary SLO, use a fair lock policy.

§What These Numbers Mean

The load benchmark compares lock policy while keeping the synthetic sharded design the same. It measures aggregate operation latency, not separate reader and writer wait time.

That distinction matters most when readers are continuous. A read-biased lock can raise aggregate throughput while making writer progress much worse. A fair lock can improve writer tails at a throughput cost. A purpose-built concurrent map may beat both if its exclusive path does less work than your full cache/store path.

Use these results to decide whether rblock is worth testing in your design. Use your own application benchmark to decide whether it is worth shipping.

§Production Guidance

Use this lock as a measured optimization, not as a default synchronization primitive. The best fit is a striped cache where:

  • shard count is high enough to spread contention,
  • reads dominate,
  • critical sections are CPU-only and very short,
  • write latency is allowed to be less fair than read latency,
  • benchmarks cover the real read/write mix and key distribution.

For write-heavy workloads, especially Zipf-skewed workloads, use the fair parking_lot policy or a data structure with a smaller write path.

Structs§

RawReadBiasedRwLock
Raw read-biased lock implementation.

Type Aliases§

RwLock
Read-biased RwLock.
RwLockReadGuard
Read guard returned by RwLock::read.
RwLockWriteGuard
Write guard returned by RwLock::write.