# distributed-lock-rs
[](https://github.com/XuHaoJun/distributed-lock-rs/actions)
[](https://crates.io/crates/distributed-lock)
[](https://docs.rs/distributed-lock)
[](https://www.rust-lang.org)
Distributed locks for Rust with multiple backend support. This library provides distributed synchronization primitives (mutex locks, reader-writer locks, semaphores) that work across processes and machines.
## Features
- 🔒 **Exclusive Locks**: Mutual exclusion across processes
- 📖 **Reader-Writer Locks**: Multiple readers or single writer
- 🎫 **Semaphores**: Limit concurrent access to N processes
- ⚡ **Async/Await**: Full async support with tokio
- 🔄 **Backend Agnostic**: Swap backends without changing application code
- 🔍 **Handle Loss Detection**: Detect when locks are lost due to connection issues
- 🚀 **Production Ready**: Connection pooling, automatic lease extension, RedLock support
## Backends
### PostgreSQL
Uses PostgreSQL advisory locks. Production-ready with connection pooling and transaction-scoped locks.
```toml
[dependencies]
distributed-lock-postgres = "0.1"
tokio = { version = "1", features = ["full"] }
```
### MySQL
Uses MySQL user-defined variables and database tables. Supports reader-writer locks through automatic table creation.
**Note**: Reader-writer locks require creating a `distributed_locks` table in your MySQL database. This table is created automatically when needed, as MySQL doesn't provide built-in reader-writer lock primitives like PostgreSQL.
```toml
[dependencies]
distributed-lock-mysql = "0.1"
tokio = { version = "1", features = ["full"] }
```
Or use the meta-crate:
```toml
[dependencies]
distributed-lock = { version = "0.1", features = ["mysql"] }
tokio = { version = "1", features = ["full"] }
```
### Redis
Uses Redis with RedLock algorithm for multi-server deployments. Supports semaphores and automatic lease extension.
```toml
[dependencies]
distributed-lock-redis = "0.1"
tokio = { version = "1", features = ["full"] }
```
### File System
Uses OS-level file locking. Simple and requires no external services.
```toml
[dependencies]
distributed-lock-file = "0.1"
tokio = { version = "1", features = ["full"] }
```
### MongoDB
Uses MongoDB's atomic document updates and aggregation pipelines. Production-ready with support for TTL-based expiration.
```toml
[dependencies]
distributed-lock-mongo = "0.1"
tokio = { version = "1", features = ["full"] }
```
### Meta-Crate (All Backends)
Or use the meta-crate to get all backends:
```toml
[dependencies]
distributed-lock = "0.1"
tokio = { version = "1", features = ["full"] }
```
You can also enable specific backends:
```toml
[dependencies]
distributed-lock = { version = "0.1", features = ["mysql", "postgres"] }
tokio = { version = "1", features = ["full"] }
```
## Quick Start
### Basic Usage
```rust
use distributed_lock::*;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a provider (example: file backend)
let provider = FileLockProvider::builder()
.directory("/tmp/locks")
.build()?;
// Create a lock by name
let lock = provider.create_lock("my-resource");
// Acquire the lock with a timeout
let handle = lock.acquire(Some(Duration::from_secs(5))).await?;
// Critical section - we have exclusive access
println!("Doing critical work...");
// Release the lock (also happens automatically on drop)
handle.release().await?;
Ok(())
}
```
### PostgreSQL Example
```rust
use distributed_lock_postgres::PostgresLockProvider;
use distributed_lock_core::prelude::*;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider = PostgresLockProvider::builder()
.connection_string("postgresql://user:pass@localhost/db")
.build()
.await?;
let lock = provider.create_lock("my-resource");
let handle = lock.acquire(Some(Duration::from_secs(5))).await?;
// Critical section
do_work().await?;
handle.release().await?;
Ok(())
}
```
### MySQL Example
```rust
use distributed_lock_mysql::MySqlLockProvider;
use distributed_lock_core::prelude::*;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider = MySqlLockProvider::builder()
.connection_string("mysql://user:pass@localhost/db")
.build()
.await?;
// Basic mutex lock
let lock = provider.create_lock("my-resource");
let handle = lock.acquire(Some(Duration::from_secs(5))).await?;
do_work().await?;
handle.release().await?;
// Reader-writer lock (creates distributed_locks table automatically)
let rw_lock = provider.create_reader_writer_lock("shared-resource");
let read_handle = rw_lock.acquire_read(None).await?;
read_data().await;
read_handle.release().await?;
Ok(())
}
```
### Redis Example
```rust
use distributed_lock_redis::RedisLockProvider;
use distributed_lock_core::prelude::*;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let provider = RedisLockProvider::builder()
.add_server("redis://localhost:6379")
.build()
.await?;
let lock = provider.create_lock("my-resource");
let handle = lock.acquire(Some(Duration::from_secs(5))).await?;
// Lock is automatically extended in the background
do_long_running_work().await?;
handle.release().await?;
Ok(())
}
```
### MongoDB Example
```rust
use distributed_lock_mongo::MongoDistributedLock;
use distributed_lock_core::prelude::*;
use std::time::Duration;
use mongodb::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::with_uri_str("mongodb://localhost:27017").await?;
let database = client.database("my_database");
let lock = MongoDistributedLock::new(
"my-resource".to_string(),
database,
None, // Default collection "DistributedLocks"
None // Default options
);
let handle = lock.acquire(Some(Duration::from_secs(5))).await?;
do_work().await?;
handle.release().await?;
Ok(())
}
```
### Reader-Writer Locks
```rust
use distributed_lock_postgres::PostgresLockProvider;
use distributed_lock_core::prelude::*;
async fn cache_example(provider: &PostgresLockProvider) -> Result<(), LockError> {
let rw_lock = provider.create_reader_writer_lock("cache");
// Multiple readers can hold the lock simultaneously
{
let read_handle = rw_lock.acquire_read(None).await?;
let data = read_from_cache().await;
read_handle.release().await?;
}
// Writers get exclusive access
{
let write_handle = rw_lock.acquire_write(None).await?;
update_cache().await;
write_handle.release().await?;
}
Ok(())
}
```
### Semaphores
```rust
use distributed_lock_redis::RedisLockProvider;
use distributed_lock_core::prelude::*;
use std::time::Duration;
async fn rate_limit_example(provider: &RedisLockProvider) -> Result<(), LockError> {
// Allow at most 5 concurrent database connections
let semaphore = provider.create_semaphore("db-pool", 5);
// Acquire a "ticket"
let ticket = semaphore.acquire(Some(Duration::from_secs(10))).await?;
// Use the limited resource
query_database().await?;
// Release the ticket
ticket.release().await?;
Ok(())
}
```
## Try-Acquire Pattern
Use `try_acquire` when you want to check if a lock is available without waiting:
```rust
match lock.try_acquire().await? {
Some(handle) => {
// We got the lock!
do_work().await;
handle.release().await?;
}
None => {
// Someone else has it
println!("Lock unavailable");
}
}
```
## Error Handling
```rust
use distributed_lock_core::prelude::*;
match lock.acquire(Some(Duration::from_secs(5))).await {
Ok(handle) => {
// Got the lock
handle.release().await?;
}
Err(LockError::Timeout(duration)) => {
eprintln!("Timed out after {:?}", duration);
}
Err(LockError::Connection(e)) => {
eprintln!("Connection error: {}", e);
}
Err(e) => {
eprintln!("Error: {}", e);
}
}
```
## Architecture
This library is organized as a Cargo workspace with separate crates:
- `distributed-lock-core`: Core traits and types
- `distributed-lock-file`: File system backend
- `distributed-lock-mysql`: MySQL backend
- `distributed-lock-postgres`: PostgreSQL backend
- `distributed-lock-redis`: Redis backend
- `distributed-lock-mongo`: MongoDB backend
- `distributed-lock`: Meta-crate re-exporting all backends
Each backend implements the same trait interfaces, allowing you to swap backends without changing application code.
## Requirements
- Rust 1.88 or later
- Tokio async runtime (for async operations)
- Backend-specific requirements:
- **PostgreSQL**: PostgreSQL 9.5+ with `pg_advisory_lock` support
- **MySQL**: MySQL 5.7+ or MariaDB 10.0+ (reader-writer locks create a `distributed_locks` table)
- **Redis**: Redis 2.6+ (Redis 3.0+ recommended)
- **MongoDB**: MongoDB 4.4+ (uses aggregation pipelines in updates)
- **File**: Any POSIX-compliant filesystem
## License
Licensed under the MIT License ([LICENSE](LICENSE) or http://opensource.org/licenses/MIT).
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## Acknowledgments
This library is inspired by the [DistributedLock](https://github.com/madelson/DistributedLock) C# library, ported to Rust with idiomatic patterns and async/await support.