| Crates.io | justalock-client |
| lib.rs | justalock-client |
| version | 0.1.0 |
| created_at | 2025-08-22 01:07:41.253857+00 |
| updated_at | 2025-08-22 01:07:41.253857+00 |
| description | A distributed lock powered by the justalock service |
| homepage | https://justalock.dev/ |
| repository | https://github.com/goakley/justalock-clients/ |
| max_upload_size | |
| id | 1805763 |
| size | 90,431 |
A Rust client library for the justalock distributed lock service. This library provides distributed locking functionality to coordinate work across multiple processes or services using Rust's async/await patterns.
CancellationToken for graceful shutdown when locks are lostAdd this to your Cargo.toml:
[dependencies]
justalock-client = "0.1"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
use justalock_client::Lock;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a lock with a unique identifier
let lock = Lock::builder(42u128).build()?;
// Execute work while holding the lock
let result = lock.locked(|cancellation_token| async move {
// Your critical work here - only runs when you have the lock
println!("I have the lock! Doing important work...");
// Check for cancellation (lock lost) during long operations
for i in 1..=5 {
if cancellation_token.is_cancelled() {
return format!("Work cancelled at step {}", i);
}
// Simulate some work
tokio::time::sleep(Duration::from_millis(200)).await;
println!("Completed step {}", i);
}
"Work completed successfully!"
}).await?;
println!("Result: {}", result);
Ok(())
}
The library uses a builder pattern for configuring locks:
use justalock_client::Lock;
let lock = Lock::builder(lock_id)
.client_id(b"my-service-v1".to_vec()) // Identify this client
.lifetime_seconds(30) // Lock expires after half a minute
.build()?;
The library supports multiple lock ID formats based on Rust primitives:
// Numeric IDs (recommended for performance)
let locku = Lock::builder(42u128);
let locki = Lock::builder(-123i128);
// Byte arrays for custom IDs
let locka = Lock::builder([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
The library provides a CancellationToken that signals when the lock is lost:
let result = lock.locked(|cancellation_token| async move {
// Long-running work with cancellation checks
for chunk in data_chunks {
if cancellation_token.is_cancelled() {
return Err("Lock lost during processing");
}
process_chunk(chunk).await?;
}
Ok("All chunks processed")
}).await?;
use std::env;
// Use consistent client IDs for the same service instance
let client_id = format!(
"{}-{}-{}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
hostname::get().unwrap().to_string_lossy()
).into_bytes();
let lock = Lock::builder(lock_id)
.client_id(client_id)
.build()?;
locked() multiple timesuse justalock_client::Lock;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let lock = Lock::builder("daily-report").build()?;
let result = lock.locked(|_token| async move {
println!("Generating daily report...");
generate_report().await
}).await?;
println!("Report generated: {}", result);
Ok(())
}
async fn generate_report() -> String {
// Only one instance will generate the report
"Report completed"
}
use justalock_client::Lock;
use std::time::Duration;
// Service A - Database migration
async fn run_migration() -> Result<(), Box<dyn std::error::Error>> {
let lock = Lock::builder("db-migration-v2")
.client_id(b"migration-service".to_vec())
.lifetime_seconds(1800) // 30 minutes
.build()?;
lock.locked(|token| async move {
println!("Starting database migration...");
let steps = ["backup", "schema_update", "data_migration", "verification"];
for (i, step) in steps.iter().enumerate() {
if token.is_cancelled() {
return Err(format!("Migration cancelled at step: {}", step));
}
println!("Step {}: {}", i + 1, step);
tokio::time::sleep(Duration::from_secs(30)).await; // Simulate work
}
println!("Migration completed successfully!");
Ok(())
}).await?
}
// Service B - Cache warming (waits for migration)
async fn warm_cache() -> Result<(), Box<dyn std::error::Error>> {
let lock = Lock::builder("db-migration-v2") // Same lock ID
.client_id(b"cache-service".to_vec())
.lifetime_seconds(300)
.build()?;
lock.locked(|_token| async move {
println!("Migration complete, warming cache...");
// Cache warming logic
Ok(())
}).await?
}
use justalock_client::Lock;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let lock = Lock::builder("hourly-cleanup")
.client_id(format!("cleanup-{}", std::process::id()).as_bytes().to_vec())
.lifetime_seconds(3600) // 1 hour max
.build()?;
loop {
// Try to acquire the lock for cleanup
match lock.clone().locked(|token| async move {
println!("Starting cleanup...");
// Cleanup tasks
let tasks = ["temp_files", "old_logs", "expired_cache"];
for task in tasks {
if token.is_cancelled() {
return format!("Cleanup cancelled at: {}", task);
}
cleanup_task(task).await;
}
"Cleanup completed"
}).await {
Ok(result) => println!("Cleanup result: {}", result),
Err(e) => println!("Cleanup failed: {:?}", e),
}
// Wait before next cleanup attempt
tokio::time::sleep(Duration::from_secs(3600)).await;
}
}
async fn cleanup_task(task: &str) {
println!("Cleaning up: {}", task);
tokio::time::sleep(Duration::from_millis(500)).await;
}
use justalock_client::{Error, Lock};
async fn robust_operation() -> Result<String, Box<dyn std::error::Error>> {
let lock = Lock::builder("critical-operation").build()?;
match lock.locked(|token| async move {
// Your critical operation
perform_critical_work(token).await
}).await {
Ok(result) => Ok(result),
Err(Error::Data(msg)) => {
eprintln!("Client error: {}", msg);
Err("Configuration error".into())
},
Err(Error::Reqwest(e)) => {
eprintln!("Network error: {}", e);
Err("Network unavailable".into())
},
}
}
async fn perform_critical_work(
token: tokio_util::sync::CancellationToken
) -> Result<String, &'static str> {
for i in 1..=10 {
if token.is_cancelled() {
return Err("Operation cancelled");
}
// Simulate work
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
Ok("Operation completed".to_string())
}
The library includes comprehensive tests with mocked API calls.
Run the test suite:
# Run all tests
cargo test
# Run with output
cargo test -- --nocapture
# Test specific scenarios
cargo test --test scenario_tests
cargo build
cargo run --example basic_usage
cargo doc --open