Skip to main content

ash_lock/
lib.rs

1//! # ash-lock — distributed coordination for the `ash-*` ecosystem
2//!
3//! **Status:** the default build is single-process ([`InMemoryBackend`]). Real
4//! distributed locking is provided by the **`etcd` feature** (`EtcdBackend`:
5//! etcd leases + transactions, with cluster-wide monotonic revision-based
6//! fencing tokens). Some collaborators named in the original spec are still
7//! stubbed:
8//!
9//! | Spec dependency | Status in this crate |
10//! |-----------------|----------------------|
11//! | lock backend | [`InMemoryBackend`] (default) or `EtcdBackend` (`etcd` feature) |
12//! | `ash-telemetry` | replaced by the in-crate [`telemetry`] counter module |
13//! | `ash-discovery` | seeds are recorded but unused; [`LockClient::connect`] runs in-process |
14//! | `ash-time` (HLC) | **adopted** behind the `ash-time` feature: stamps every acquire — see [`clock`] |
15//! | `ash-flare` | supervision contract honoured by the internal watchdog; no `ash-flare` dependency yet |
16//! | `ash-rpc` | remote backend transport not implemented |
17//!
18//! ## Fencing tokens — READ THIS
19//!
20//! A distributed lock **cannot** prevent a stalled lock holder (GC pause, paused
21//! VM) from waking up and writing after its lease has already expired and been
22//! re-granted. The only robust defence is a *fencing token*: a strictly
23//! monotonic number handed out on every acquire, which you attach to every write
24//! to the protected resource. The resource rejects any write whose token is
25//! lower than the highest it has seen. Use [`validate_write`] at the resource.
26//!
27//! ```
28//! # async fn ex() -> ash_lock::Result<()> {
29//! use ash_lock::{LockClient, LockConfig, validate_write};
30//! use std::time::Duration;
31//!
32//! let client = LockClient::connect(LockConfig {
33//!     default_ttl: Duration::from_secs(30),
34//!     watchdog_interval: Duration::from_secs(10),
35//!     ..Default::default()
36//! }).await?;
37//!
38//! let guard = client.lock("orders/42").await.with_ttl(Duration::from_secs(20)).acquire().await?;
39//! let token = guard.fencing_token()?;      // attach `token` to every write
40//! assert!(validate_write(token, token));   // resource-side check
41//! // ... do work ...
42//! guard.unlock().await?;                    // or just drop `guard`
43//! # Ok(()) }
44//! ```
45
46#![forbid(unsafe_code)]
47
48pub mod backend;
49mod builder;
50mod client;
51pub mod clock;
52mod error;
53mod guard;
54pub mod telemetry;
55mod watchdog;
56
57#[cfg(feature = "etcd")]
58pub use backend::etcd::EtcdBackend;
59pub use backend::{InMemoryBackend, LockBackend};
60pub use builder::LockBuilder;
61pub use client::{LockClient, LockConfig};
62pub use clock::Stamp;
63// Re-exported so `LockBuilder`'s borrow type is nameable, and for advanced use.
64pub(crate) use client::ClientInner;
65pub use error::{Error, Result};
66pub use guard::{LockGuard, validate_write};
67
68#[cfg(test)]
69mod dst;