| Crates.io | bitcoinleveldb-limiter |
| lib.rs | bitcoinleveldb-limiter |
| version | 0.1.19 |
| created_at | 2023-01-18 20:33:48.322789+00 |
| updated_at | 2025-12-01 17:01:50.518225+00 |
| description | Lock-free atomic limiter used to cap concurrent resource usage (e.g., file descriptors and mmaps) in Bitcoin LevelDB integrations, providing a non-blocking counter-based semaphore with debug-time misuse detection. |
| homepage | |
| repository | https://github.com/klebs6/bitcoin-rs |
| max_upload_size | |
| id | 762035 |
| size | 189,892 |
A small, focused concurrency primitive for bounding resource consumption in the Bitcoin LevelDB stack. It provides a lock-free, atomic counter–based limiter intended for constraining scarce process resources such as file descriptors and mmapped regions.
bitcoinleveldb-limiter exposes a single type, Limiter, which enforces a hard upper bound on the number of concurrently held resources. It is designed for scenarios where allocation is cheap but oversubscription has catastrophic or non-linear system cost (e.g., exhausting file descriptors, virtual address space, or triggering kernel pathologies under massive FD counts).
The limiter is:
Atomic<i32> with relaxed ordering for minimal contention.bool), leaving blocking/retry policy to the caller.acquire() must be paired with exactly one release().Conceptually, Limiter is an integer-valued resource semaphore with non-blocking acquisition and a compile-time known capacity.
use bitcoinleveldb_limiter::Limiter;
/// Construct a limiter with a maximum of `max_acquires` concurrent resources.
/// Negative values are clamped to 0.
pub fn new(max_acquires: i32) -> Limiter;
/// Attempt to acquire one unit of capacity.
///
/// Returns `true` if a resource was successfully acquired, `false` if the
/// limiter is at capacity and no additional resource can be granted.
pub fn acquire(&self) -> bool;
/// Release one unit of capacity previously acquired by `acquire()`.
///
/// Must only be called after a successful `acquire()`. Over-release is
/// detected in debug builds via `debug_assert!`.
pub fn release(&self);
new(max_acquires)
max_acquires >= 0: the limiter starts with exactly max_acquires available slots.max_acquires < 0: a warning is logged and capacity is clamped to 0.acquire()
fetch_sub(1, Relaxed).true.<= 0, capacity is immediately restored via a compensating fetch_add(1, Relaxed) and the call returns false.release() calls) triggers a debug_assert!.release()
fetch_add(1, Relaxed).max_acquires, a debug_assert! fires to signal over-release.This yields a simple invariant in debug mode:
[ 0 \leq \text{acquires_allowed} \leq \text{max_acquires} ]
when all clients pair acquisitions and releases correctly.
use bitcoinleveldb_limiter::Limiter;
use std::fs::File;
use std::io;
use std::path::Path;
fn open_bounded<P: AsRef<Path>>(limiter: &Limiter, path: P) -> io::Result<Option<File>> {
if !limiter.acquire() {
// At capacity: caller decides whether to block, backoff, or degrade.
return Ok(None);
}
let file = File::open(path);
// Ensure capacity is returned even on error paths.
if file.is_err() {
limiter.release();
}
Ok(file.ok())
}
Wrap the limiter into a higher-level RAII guard so you cannot forget to release:
use bitcoinleveldb_limiter::Limiter;
use std::sync::Arc;
pub struct Permit {
limiter: Arc<Limiter>,
}
impl Drop for Permit {
fn drop(&mut self) {
self.limiter.release();
}
}
impl Permit {
pub fn try_acquire(limiter: Arc<Limiter>) -> Option<Self> {
if limiter.acquire() {
Some(Permit { limiter })
} else {
None
}
}
}
This pattern is particularly valuable under complex control flow, where manual bookkeeping is error-prone.
Because acquire() is non-blocking, the caller can implement any policy for handling saturation:
This design cleanly separates resource accounting (the concern of Limiter) from scheduling and latency (the concern of the caller or surrounding framework).
Limiter employs Atomic<i32> with Ordering::Relaxed. This is adequate because:
Limiter to publish or synchronize additional data beyond the availability of capacity.If you use the limiter as part of a synchronization protocol that also coordinates additional shared state, you must introduce the appropriate memory ordering or separate synchronization primitives outside of Limiter.
As with any non-blocking primitive, Limiter does not provide fairness guarantees: under heavy contention, some threads may experience repeated acquisition failures even while others succeed.
Limiter uses debug_assert! for internal consistency checks:
release() called more times than successful acquire() calls) is detected.acquire() / release() symmetry manifests early in debug builds.These checks are removed in --release builds, so you should validate your usage under debug configuration during development.
Logging (trace!, debug!, warn!) is employed for observability and can be integrated with a structured logging backend. In production, you typically reduce the log level to keep overhead minimal.
bitcoin-rs RepositoryThis crate originates from the bitcoin-rs project and mirrors the resource limiting semantics used in Bitcoin Core's LevelDB integration. Its primary application is preventing pathological resource usage in large on-disk databases while keeping the limiting primitive self-contained and composable.
Repository: https://github.com/klebs6/bitcoin-rs
LimiterLimiter is appropriate when:
You might prefer a different abstraction when:
bitcoinleveldb-limiter deliberately focuses on a single, precise responsibility: safe, low-latency enforcement of a global concurrency bound.