Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
bitcoin-sync
High‑fidelity Rust reimplementation of Bitcoin Core’s low‑level synchronization and threading primitives.
This crate mirrors the semantics of Bitcoin Core’s C++ locking, semaphore, and thread‑interrupt machinery while exposing an idiomatic Rust API built on top of parking_lot and tracing. It is intended for building Bitcoin‑style concurrent runtimes, particularly where lock‑order correctness and deterministic behaviour matter more than maximal abstraction.
Design goals
- Semantic compatibility with Bitcoin Core
- Similar behaviour to
CCriticalSection,CThreadInterrupt,TraceThread, and related helpers. - No poisoning semantics on mutex failure, matching Core’s model (panic ≠ permanent lock poisoning).
- Similar behaviour to
- Strong debugging & lock‑order diagnostics
- Optional early deadlock detection via a global lock‑graph (when compiled with
DEBUG_LOCKORDER). - Assertions that verify which locks are held/not‑held at specific call sites.
- Optional early deadlock detection via a global lock‑graph (when compiled with
- Minimal, explicit primitives
- Thin wrappers around
parking_lotraw mutexes and condition variables. - RAII helpers (
UniqueLock,ReverseLock,SemaphoreGrant, etc.) instead of opaque higher‑level frameworks.
- Thin wrappers around
- Predictable threading behaviour
- Structured thread launch wrapper that logs lifecycle and propagates panics.
- Interruptible sleeper abstraction (
ThreadInterrupt) with well‑defined timing semantics.
This crate is deliberately low level. It is intended for experts who need precise control over lock ordering and cross‑thread coordination rather than generic application‑level concurrency.
Features overview
1. Lock abstraction: LockApi and AnnotatedMixin
LockApi represents the minimal interface expected from a raw, non‑poisoning mutex. Any type implementing this trait can plug into the higher‑level RAII guards provided by this crate.
AnnotatedMixin<Parent> wraps a Parent: LockApi and:
- Forwards all lock operations.
- Provides a stable address and hook point for lock‑order tracking and future instrumentation.
The primary concrete aliases are:
/// Recursive lock (reentrant), no waiting (raw mutex semantics).
pub type RecursiveMutex<T> = ;
/// Non‑recursive raw mutex supporting waiting.
pub type Mutex = ;
AnnotatedMixin<parking_lot::RawMutex> implements Default with RawMutex::INIT so it can be used as a field with default construction.
2. RAII locking: UniqueLock and ReverseLock
UniqueLock
UniqueLock<'a, M> is a RAII guard analogous to C++ std::unique_lock specialized for M: LockApi + ?Sized:
Key operations:
new(mutex, name, file, line, try_: Option<bool>)– constructs and either blocks (lock) or attemptstry_lockdepending ontry_.enter()– acquire the lock if not already owned.try_enter() -> bool– try to acquire; returns whether ownership was gained.unlock()– explicitly release if currently owned.owns_lock() -> bool– mirror of C++’sowns_lock.Drop– automatically unlocks if still owned.
It also implements From<&UniqueLock<…>> for bool, so you can write:
let locked: bool = .into;
The struct records name, file, and line, allowing detailed logging of lock acquisition and release events via tracing macros inside the implementation.
ReverseLock
ReverseLock is a scoped inversion helper:
ReverseLock::new(&mut guard)unlocks the underlying mutex immediately.- On
Drop, it re‑enters the lock (if not already re‑locked).
This pattern is essential in Core’s codebase for temporarily releasing a lock around a potentially blocking or re‑entrant operation without losing the association with the guard variable.
3. Macros for critical sections and lock assertions
The crate exposes several macros to express critical sections, multi‑lock scopes, and assertions that a lock is (or is not) held.
Scope guards
lock!; // creates an unnamed UniqueLock bound to `cs`
lock2!; // lock two mutexes in sequence
try_lock!; // guard: UniqueLock with try=Some(true)
wait_lock!; // guard: UniqueLock with blocking lock
with_lock!;
The lock! and lock2! macros use the paste crate to synthesize a unique guard name linked to the source line, ensuring RAII destruction at scope exit.
with_lock! is syntactic sugar which:
- Acquires a
UniqueLockon$csvialock!. - Executes
$codewhile the lock is held.
Critical section tracking
Two macros integrate with the lock‑order tracking internals (see the debug_lockorder module):
enter_critical_section!;
leave_critical_section!;
enter_critical_section!(cs)records metadata about the acquisition (name, file, line, address) and then callscs.lock().leave_critical_section!(cs)verifies thatcsis the most recent critical section lock before unlocking and notifying the lock‑order system.
These macros allow the DEBUG_LOCKORDER logic to build a global partial order on lock acquisition, enabling early detection of potential deadlocks by identifying cycles in the lock graph.
Lock assertions
assert_lock_held!;
assert_lock_not_held!;
These macros call into assert_lock_held_internal / assert_lock_not_held_internal, which, under DEBUG_LOCKORDER, verify that cs is (or is not) present in the thread‑local lock stack. On failure they emit detailed diagnostics and either abort or panic depending on configuration.
4. Semaphore & permits: Semaphore and SemaphoreGrant
Semaphore is a simple counting semaphore built on top of a Condvar and a raw Mutex<i32>:
``
Operations:
- `new` – create with an initial permit count .
- `wait` – block until `count > 0`, then decrement.
- `try_wait ` – non‑blocking attempt; `true` if a permit was consumed.
- `post` – increment `count` and wake one waiter.
`SemaphoreGrant` is a RAII wrapper around a single permit:
```rust
Semantics:
SemaphoreGrant::new(sema: Arc<Semaphore>, try_: Option<bool>)– constructs a grant and either blocks or tries to acquire immediately.acquire(&mut self)– blocking acquisition if not already holding a permit.try_acquire(&mut self) -> bool– attempt acquisition without blocking.release(&mut self)– return the permit if currently held.move_to(&mut self, target: &mut SemaphoreGrant)– transfer ownership of a permit totarget.Drop– automatically releases any held permit.
Like UniqueLock, SemaphoreGrant implements From<&SemaphoreGrant> for bool so it can be treated as a truth value indicating permit ownership.
WaitTimedOut(bool) is a thin wrapper with .timed_out() -> bool to express timeout results in a self‑documenting fashion.
5. Thread management: launch_traced_thread! and trace_thread
launch_traced_thread!
launch_traced_thread!;
Expands to std::thread::Builder::new().name(...).spawn(...) and delegates the body to trace_thread, panicking immediately if thread creation fails.
trace_thread
- Creates a
tracingspan named"thread"with fieldname = thread_name. - Logs
"<name> thread start"and"<name> thread exit"atINFOlevel. - Wraps the user closure in
std::panic::catch_unwind. If it panics, it logs the panic and re‑raises, preserving default Rust semantics while still emitting structured telemetry.
This is a direct analogue of Bitcoin Core’s TraceThread helper.
6. Interruptible sleeping: ThreadInterrupt
ThreadInterrupt models CThreadInterrupt from Bitcoin Core:
Core semantics:
new()– create withflag = false.as_bool() -> bool– observe the interrupt flag withAcquireordering.reset()– clear any pending interrupt (Releaseordering).invoke()– set the flag true andnotify_all()on the condition variable.sleep_for(rel_time: StdDuration) -> bool:- Returns
falseif an interrupt happens before the full timeout elapses. - Returns
trueif the fullrel_timepasses without interruption.
- Returns
Implementation detail: sleep_for loops, computing the remaining time until a pre‑computed deadline and calling Condvar::wait_for. After each wakeup it re‑checks the interrupt flag and the clock.
This primitive is useful for threads that must be cooperatively cancellable while still respecting a bounded maximum sleep.
7. Low‑level waiting: wait_until
Sized, P> where
P: FnMut ,
This helper repeatedly waits on cv until either:
predicate()becomestrue, in which case it returnstrue, or- the
deadlineis reached and a final predicate check still fails, in which case it returns that final predicate result.
The pattern corresponds to the standard condition‑variable idiom:
[ \text{while } \neg P \text{ and } t < T_{\text{deadline}}: \text{ wait}. ]
Mathematically, this implements a partial function f: (P, T_deadline) -> bool where the result is the final valuation of P at or after the deadline, ensuring spurious wakeups do not violate the semantics.
8. Scoped raw mutex: ScopedRawMutex and ScopedRawMutexGuard
For scenarios requiring direct access to a raw parking_lot::RawMutex while still benefiting from RAII unlocking:
;
ScopedRawMutex::default()– constructs withRawMutex::INIT.ScopedRawMutex::lock(&self) -> ScopedRawMutexGuard<'_>– locks the underlying raw mutex, returning a guard.- On
DropofScopedRawMutexGuard,RawMutexTrait::unlockis invoked.
This is intentionally minimal; it exists primarily as a bridge towards the C++‑style code generation and binding layers.
9. Debug lock‑order analysis (DEBUG_LOCKORDER)
When built with DEBUG_LOCKORDER, the crate enables an internal module that maintains a global lock‑graph to detect:
- Double‑locking of non‑recursive mutexes on the same thread.
- Lock order inversions across threads (potential deadlocks).
Key concepts:
- LockLocation – describes where a lock was acquired: mutex name, source file, line, thread name, and whether the acquisition was a try‑lock.
- LockStack – per‑thread stack of
(mutex_ptr, LockLocation)pairs. - LockOrders – a map from ordered pairs
(A, B)to the stack snapshot whenBwas acquired afterA. - InvLockOrders – set of the reversed pairs
(B, A)for fast detection of inversions.
Algorithmically, each time a lock is pushed, the module:
- Checks for double lock: if the same pointer already appears in the stack, it reports a double lock.
- For each previously held lock
Lin the current stack, considers pair(L, current):- If
(current, L)is already inlockorders, a cycle exists → potential deadlock. - Otherwise inserts
(L, current)intolockordersand(current, L)intoinvlockorders.
- If
In both double‑lock and potential‑deadlock cases, a detailed textual dump of the involved lock stacks is produced, and abort_or_panic is called. A global AtomicBool G_DEBUG_LOCKORDER_ABORT determines whether to abort the process or panic.
In non‑debug builds (cfg(not(DEBUG_LOCKORDER))), the functions become no‑ops, so there is no runtime overhead beyond the macro calls themselves.
Example usage
Basic mutex usage with RAII
use ;
static GLOBAL: Mutex = default;
``
### Using the helper macros
```rust
use ;
use ;
static COUNTER_LOCK: Mutex = default;
static mut COUNTER: i32 = 0;
Semaphore and grants
use Arc;
use ;
Interruptible worker thread
use Arc;
use Duration;
use ;
Crate metadata
- Crate:
bitcoin-sync - Version:
0.1.19 - Repository: https://github.com/klebs6/bitcoin-rs
- License: MIT
- Edition: Rust 2021
This crate is designed as a building block within a larger Bitcoin reimplementation effort; its public API focuses on concurrency primitives, not high‑level Bitcoin logic.
When to use this crate
Use bitcoin-sync if you:
- Are porting or closely emulating Bitcoin Core behaviour in Rust and need compatible synchronization primitives.
- Need early deadlock detection and explicit lock‑order tracking similar to Core’s
DEBUG_LOCKORDERtooling. - Prefer explicit, RAII‑based concurrency constructs over more abstract frameworks.
If you only need generic high‑level Rust concurrency, standard library types (Mutex, RwLock, Condvar) or tokio/async primitives may be more appropriate. bitcoin-sync is primarily for systems programmers who require tight control over lock semantics and diagnostics.