Skip to content

Commit 5974ebf

Browse files
committed
first commit
0 parents  commit 5974ebf

File tree

10 files changed

+208
-0
lines changed

10 files changed

+208
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/target
2+
/Cargo.lock
3+
/.idea/
4+
.DS_Store

Cargo.toml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "dist_lock"
3+
version = "0.0.1"
4+
authors = ["chenglong.wang <[email protected]>"]
5+
edition = "2021"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[workspace]
10+
11+
[dependencies]
12+
chrono = "0.4.26"
13+
thiserror = "1.0.44"
14+
redis = { version = "0.23.0", optional = true }
15+
tokio = { version = "1.29.1", optional = true }
16+
async-trait = "0.1.72"
17+
18+
[features]
19+
default = ["redis_tokio_provider"]
20+
redis_tokio_provider = ["redis/tokio-comp", "tokio/rt"]
21+
thread = []

rust-toolchain.toml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[toolchain]
2+
channel = "nightly"
3+
components = ["rustfmt", "clippy", "rust-analyzer"]

rustfmt.toml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
hard_tabs = true
2+
max_width = 100
3+
use_small_heuristics = "Max"
4+
imports_granularity = "Item"
5+
reorder_imports = true
6+
# Format comments
7+
comment_width = 100
8+
wrap_comments = true
9+
edition = "2021"

src/core/local_cache.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use chrono::DateTime;
2+
use chrono::Utc;
3+
4+
#[cfg(feature = "thread")]
5+
use core::cell::Cell;
6+
7+
use crate::provider::redis::RedisLock;
8+
9+
#[cfg(feature = "thread")]
10+
thread_local! {
11+
static LOCKED: Cell<DateTime<Utc>> = Cell::new(Default::default());
12+
}
13+
14+
#[cfg(feature = "tokio")]
15+
tokio::task_local! {
16+
static LOCKED: DateTime<Utc>;
17+
}
18+
19+
pub fn set_local(time: DateTime<Utc>) {
20+
#[cfg(feature = "thread")]
21+
LOCKED.with(|cell| cell.set(time));
22+
23+
#[cfg(feature = "tokio")]
24+
LOCKED.sync_scope(time, || {});
25+
}
26+
27+
pub fn get_local() -> DateTime<Utc> {
28+
#[cfg(feature = "thread")]
29+
let value = LOCKED.with(|cell| cell.get());
30+
31+
#[cfg(feature = "tokio")]
32+
let value = LOCKED.get();
33+
value
34+
}
35+
36+
impl<T> Drop for RedisLock<'_, T> {
37+
fn drop(&mut self) {
38+
set_local(Default::default());
39+
}
40+
}

src/core/mod.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use async_trait::async_trait;
2+
use chrono::Duration;
3+
4+
use crate::error::LockResult;
5+
6+
pub mod local_cache;
7+
8+
#[derive(Debug)]
9+
pub struct LockConfig {
10+
pub name: String,
11+
pub min_lock: Duration,
12+
pub max_lock: Duration,
13+
}
14+
15+
#[async_trait]
16+
pub trait Lockable<T>: Sized {
17+
async fn acquire(&mut self) -> LockResult<bool>;
18+
19+
async fn release(&self) -> LockResult<bool>;
20+
21+
async fn extend(&mut self) -> LockResult<bool>;
22+
}

src/error.rs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
pub type LockResult<T> = core::result::Result<T, crate::error::LockError>;
2+
3+
#[derive(Debug, thiserror::Error)]
4+
pub enum LockError {
5+
#[error("Redis error: {0}")]
6+
RedisError(#[from] redis::RedisError),
7+
8+
#[error("lock failed")]
9+
LockFailed,
10+
}

src/lib.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
pub mod core;
2+
pub mod error;
3+
pub mod provider;
4+
5+
#[cfg(test)]
6+
mod tests {
7+
use chrono::Duration;
8+
use chrono::Utc;
9+
10+
#[test]
11+
fn it_works() {
12+
let a = Duration::max_value();
13+
let _b = Utc::now() + a;
14+
}
15+
}

src/provider/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod redis;

src/provider/redis.rs

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use async_trait::async_trait;
2+
use chrono::DateTime;
3+
use chrono::Utc;
4+
use redis::Client;
5+
use redis::Value;
6+
7+
use crate::core::LockConfig;
8+
use crate::core::Lockable;
9+
use crate::error::LockResult;
10+
11+
const KEY_PREFIX: &str = "dist_lock";
12+
13+
#[derive(Debug)]
14+
pub struct RedisLock<'a, T> {
15+
pub key: String,
16+
pub config: LockConfig,
17+
pub driver: &'a T,
18+
pub create_at: DateTime<Utc>,
19+
pub locked_at: Option<DateTime<Utc>>,
20+
}
21+
22+
impl<'a, T> RedisLock<'a, T> {
23+
pub fn new(config: LockConfig, driver: &'a T) -> Self {
24+
RedisLock {
25+
key: format!("{}:{}", KEY_PREFIX, &config.name),
26+
config,
27+
driver,
28+
create_at: Utc::now(),
29+
locked_at: None,
30+
}
31+
}
32+
}
33+
34+
#[async_trait]
35+
impl<'a> Lockable<Client> for RedisLock<'a, Client> {
36+
async fn acquire(&mut self) -> LockResult<bool> {
37+
let mut conn = self.driver.get_async_connection().await?;
38+
let value: Value = redis::cmd("SET")
39+
.arg(&self.key)
40+
.arg(Utc::now().timestamp_millis())
41+
.arg("NX")
42+
.arg("PX")
43+
.arg(self.config.max_lock.num_milliseconds() as usize)
44+
.query_async(&mut conn)
45+
.await?;
46+
47+
match value {
48+
Value::Okay => {
49+
self.locked_at = Some(Utc::now());
50+
Ok(true)
51+
}
52+
_ => Ok(false),
53+
}
54+
}
55+
56+
async fn release(&self) -> LockResult<bool> {
57+
let now = Utc::now();
58+
let elapsed = now - self.locked_at.unwrap_or_default();
59+
let remaining = self.config.min_lock - elapsed;
60+
let mut conn = self.driver.get_async_connection().await?;
61+
let value: Value = if remaining.num_milliseconds() > 0 {
62+
redis::cmd("SET")
63+
.arg(&self.key)
64+
.arg(Utc::now().timestamp_millis())
65+
.arg("XX")
66+
.arg("PX")
67+
.arg((self.config.min_lock - remaining).num_milliseconds())
68+
.query_async(&mut conn)
69+
.await?
70+
} else {
71+
redis::cmd("DEL").arg(&self.key).query_async(&mut conn).await?
72+
};
73+
74+
match value {
75+
Value::Okay => Ok(true),
76+
_ => Ok(false),
77+
}
78+
}
79+
80+
async fn extend(&mut self) -> LockResult<bool> {
81+
todo!()
82+
}
83+
}

0 commit comments

Comments
 (0)