Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## Unreleased

* Add `clients::Locker` distributed-lock interface behind a new `locker`
feature, with automatic lease extension and RESP3 client-tracking
invalidation pushes for fast loss detection. Builder integration via
`Builder::build_locker`.

## 10.1.0

* Add `DynamicPool` interface
Expand Down
11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ features = [
"full-tracing",
"credential-provider",
"dynamic-pool",
"tcp-user-timeouts"
"tcp-user-timeouts",
"locker"
]
rustdoc-args = ["--cfg", "docsrs"]

Expand Down Expand Up @@ -67,6 +68,10 @@ credential-provider = []
dynamic-pool = ["metrics"]
tcp-user-timeouts = []

# `clients::Locker` — distributed locks with auto-extension and RESP3
# invalidation-driven loss detection (Rust port of Go's `rueidislock`).
locker = ["i-keys", "i-scripts", "i-tracking", "sha-1"]

# Enable experimental support for the Glommio runtime.
glommio = ["dep:glommio", "futures-io", "pin-project", "fred-macros/enabled", "oneshot", "futures-lite"]
# Enable experimental support for the Monoio runtime.
Expand Down Expand Up @@ -289,3 +294,7 @@ required-features = ["tokio-stream/sync", "i-std"]
[[example]]
name = "transactions"
required-features = ["transactions", "i-std"]

[[example]]
name = "locker"
required-features = ["locker", "i-std"]
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Examples
* [DNS](./dns.rs) - Customize the DNS resolution logic.
* [Client Tracking](./client_tracking.rs) -
Implement [client side caching](https://redis.io/docs/manual/client-side-caching/).
* [Locker](./locker.rs) - Acquire a distributed lock with automatic lease extension and RESP3
invalidation-driven loss detection.
* [Events](./events.rs) - Respond to connection events with the `EventsInterface`.
* [Keyspace Notifications](./keyspace.rs) - Use
the [keyspace notifications](https://redis.io/docs/manual/keyspace-notifications/) interface.
Expand Down
51 changes: 51 additions & 0 deletions examples/locker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#![allow(clippy::disallowed_names)]

use fred::{
clients::{Locker, LockerConfig},
prelude::*,
types::RespVersion,
};
use std::time::Duration;

// Run a redis server locally on the default port, then:
// cargo run --example locker --features locker
//
// The example acquires a lock, does a few seconds of "work" while the
// background extender keeps the TTL alive, and releases the lock cleanly.
#[tokio::main]
async fn main() -> Result<(), Error> {
// RESP3 is needed for the OPTOUT client-tracking fast path. To run against
// RESP2 build the locker with `LockerConfig::new().disable_caching(true)`
// and the waiter loop will fall back to polling.
let client = Builder::default_centralized()
.with_config(|c| c.version = RespVersion::RESP3)
.build()?;
client.init().await?;

let locker = Locker::from_client(
LockerConfig::new()
.key_prefix("example:lock")
.key_validity(Duration::from_secs(6)),
client.clone(),
);
locker.init().await?;

let guard = locker.acquire("demo").await?;
println!("acquired {} = {}", guard.key(), guard.token());

let work = async {
for i in 0 .. 5 {
tokio::time::sleep(Duration::from_secs(1)).await;
println!("tick {i}");
}
};
tokio::select! {
_ = work => println!("work finished"),
_ = guard.cancelled() => println!("lock lost mid-work"),
}

guard.release().await;
locker.close().await;
client.quit().await?;
Ok(())
}
Loading