wx_rust_common/util/locks/
redis_distributed_lock.rs1use std::sync::Arc;
8
9use redis::Client;
10
11#[derive(Clone)]
17pub struct RedisDistributedLock {
18 client: Client,
19 key: String,
20 lease_milliseconds: i64,
21}
22
23impl RedisDistributedLock {
24 pub fn new(client: Client, lease_milliseconds: i64) -> Result<Self, String> {
33 if lease_milliseconds <= 0 {
34 return Err(format!(
35 "Parameter 'leaseMilliseconds' must grate then 0: {lease_milliseconds}"
36 ));
37 }
38 let key = format!("lock:{}", uuid());
39 Ok(Self {
40 client,
41 key,
42 lease_milliseconds,
43 })
44 }
45
46 pub fn with_key(
53 client: Client,
54 key: impl Into<String>,
55 lease_milliseconds: i64,
56 ) -> Result<Self, String> {
57 if lease_milliseconds <= 0 {
58 return Err(format!(
59 "Parameter 'leaseMilliseconds' must grate then 0: {lease_milliseconds}"
60 ));
61 }
62 Ok(Self {
63 client,
64 key: key.into(),
65 lease_milliseconds,
66 })
67 }
68
69 pub fn lease_milliseconds(&self) -> i64 {
71 self.lease_milliseconds
72 }
73
74 pub fn key(&self) -> &str {
76 &self.key
77 }
78
79 pub fn try_lock(&self) -> Result<Option<LockGuard>, redis::RedisError> {
84 let value = uuid();
85 let mut conn = self.client.get_connection()?;
86 let result: Option<String> = redis::cmd("SET")
87 .arg(&self.key)
88 .arg(&value)
89 .arg("NX")
90 .arg("PX")
91 .arg(self.lease_milliseconds)
92 .query(&mut conn)?;
93 if result.is_some() {
94 Ok(Some(LockGuard {
95 client: self.client.clone(),
96 key: self.key.clone(),
97 value,
98 }))
99 } else {
100 Ok(None)
101 }
102 }
103
104 pub fn lock(&self, timeout: Option<std::time::Duration>) -> Result<LockGuard, String> {
112 let start = std::time::Instant::now();
113 loop {
114 match self.try_lock() {
115 Ok(Some(g)) => return Ok(g),
116 Ok(None) => {
117 if let Some(t) = timeout
118 && start.elapsed() >= t
119 {
120 return Err("acquire timeouted".to_string());
121 }
122 std::thread::sleep(std::time::Duration::from_millis(1000));
123 }
124 Err(e) => return Err(format!("lock failed: {e}")),
125 }
126 }
127 }
128}
129
130pub struct LockGuard {
132 client: Client,
133 key: String,
134 value: String,
135}
136
137impl Drop for LockGuard {
138 fn drop(&mut self) {
139 let script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
141 let mut conn = match self.client.get_connection() {
142 Ok(c) => c,
143 Err(_) => return,
144 };
145 let _: Option<i64> = redis::cmd("EVAL")
146 .arg(script)
147 .arg(1)
148 .arg(&self.key)
149 .arg(&self.value)
150 .query(&mut conn)
151 .ok();
152 }
153}
154
155fn uuid() -> String {
157 let bytes: [u8; 16] = rand::random();
159 hex::encode(bytes)
160}
161
162#[allow(dead_code)]
164fn _touch(_: Arc<()>) {}