Skip to content

Commit 5f3dc5c

Browse files
committed
feat: Add impl of #[dist_lock]
1 parent 21ce898 commit 5f3dc5c

File tree

10 files changed

+214
-31
lines changed

10 files changed

+214
-31
lines changed

Cargo.toml

+13-9
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ edition = "2021"
77
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
88

99
[workspace]
10+
members = [".", "codegen"]
1011

1112
[dependencies]
1213
chrono = "0.4.26"
1314
thiserror = "1.0.44"
1415
gethostname = "0.4.3"
1516
cfg-if = "1.0.0"
17+
dist_lock_codegen = { path = "./codegen", version = "*", optional = true }
1618
redis = { version = "0.23.0", optional = true }
1719
diesel = { version = "2.1.0", features = ["chrono"], optional = true }
1820
tokio = { version = "1.29.1", features = ["macros"], optional = true }
@@ -22,30 +24,32 @@ futures = { version = "0.3.28", optional = true }
2224
r2d2 = { version = "0.8.10", optional = true }
2325

2426
[features]
25-
default = ["diesel_mysql_r2d2"]
27+
default = ["diesel_sqlite"]
2628

2729
# redis
28-
redis_provider = ["redis/cluster"]
29-
redis_r2d2_provider = ["redis_provider", "redis/r2d2", "r2d2"]
30+
redis_provider = ["redis/cluster", "dist_lock_codegen/redis"]
31+
redis_r2d2_provider = ["redis_provider", "redis/r2d2", "r2d2", "dist_lock_codegen/redis"]
3032
redis_tokio_provider = [
3133
"redis/tokio-comp",
3234
"redis/cluster-async",
3335
"tokio",
3436
"futures",
3537
"async-trait",
38+
"dist_lock_codegen/redis"
3639
]
3740
redis_async_std_provider = [
3841
"redis/async-std-comp",
3942
"redis/cluster-async",
4043
"async-std",
4144
"futures",
4245
"async-trait",
46+
"dist_lock_codegen/redis"
4347
]
4448

4549
# diesel
46-
diesel_sqlite = ["diesel/sqlite"]
47-
diesel_postgres = ["diesel/postgres"]
48-
diesel_mysql = ["diesel/mysql"]
49-
diesel_sqlite_r2d2 = ["diesel_sqlite", "diesel/r2d2", "r2d2"]
50-
diesel_postgres_r2d2 = ["diesel_postgres", "diesel/r2d2", "r2d2"]
51-
diesel_mysql_r2d2 = ["diesel_mysql", "diesel/r2d2", "r2d2"]
50+
diesel_sqlite = ["diesel/sqlite", "dist_lock_codegen/diesel"]
51+
diesel_postgres = ["diesel/postgres", "dist_lock_codegen/diesel"]
52+
diesel_mysql = ["diesel/mysql", "dist_lock_codegen/diesel"]
53+
diesel_sqlite_r2d2 = ["diesel_sqlite", "diesel/r2d2", "r2d2", "dist_lock_codegen/diesel"]
54+
diesel_postgres_r2d2 = ["diesel_postgres", "diesel/r2d2", "r2d2", "dist_lock_codegen/diesel"]
55+
diesel_mysql_r2d2 = ["diesel_mysql", "diesel/r2d2", "r2d2", "dist_lock_codegen/diesel"]

codegen/Cargo.toml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "dist_lock_codegen"
3+
version = "0.0.1"
4+
authors = ["chenglong.wang <[email protected]>"]
5+
edition = "2021"
6+
7+
[lib]
8+
proc-macro = true
9+
10+
[dependencies]
11+
proc-macro2 = "1.0.69"
12+
quote = "1.0.33"
13+
syn = { version = "2.0.38", features = ["full"] }
14+
humantime = "2.1.0"
15+
16+
[features]
17+
redis = []
18+
diesel = []

codegen/src/impl_lock.rs

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use proc_macro2::Ident;
2+
use proc_macro2::Span;
3+
use proc_macro2::TokenStream;
4+
use quote::quote;
5+
use syn::ExprCall;
6+
use syn::Result;
7+
8+
use crate::parse_args::DistLockArgs;
9+
10+
pub(crate) fn generate(lock_args: DistLockArgs) -> Result<TokenStream> {
11+
let name = lock_args.name.value();
12+
let at_most_string = lock_args.at_most.value();
13+
let at_most = at_most_string
14+
.parse::<humantime::Duration>()
15+
.map(|d| d.as_millis() as i64)
16+
.map_err(|_| {
17+
syn::Error::new(Span::call_site(), format!("can't prase at_most: {}", at_most_string))
18+
})?;
19+
20+
let at_least_string = lock_args.at_least.map(|s| s.value()).unwrap_or("0s".to_string());
21+
let at_least = at_least_string
22+
.parse::<humantime::Duration>()
23+
.map(|d| d.as_millis() as i64)
24+
.map_err(|_| {
25+
syn::Error::new(Span::call_site(), format!("can't prase at_least: {}", at_most_string))
26+
})?;
27+
28+
Ok(gen_lock_code(name, at_most, at_least, lock_args.connection))
29+
}
30+
31+
#[cfg(feature = "redis")]
32+
fn gen_lock_code(
33+
name: String,
34+
at_most_mills: i64,
35+
at_least_mills: i64,
36+
connection: ExprCall,
37+
) -> TokenStream {
38+
let name_ident = Ident::new(&format!("_{}", &name), Span::call_site());
39+
quote! {
40+
let mut #name_ident = {
41+
use ::dist_lock::core::DistLock;
42+
use ::dist_lock::core::LockConfig;
43+
use ::dist_lock::provider::redis::RedisDriver;
44+
45+
let lock_name = #name.to_string();
46+
let driver = RedisDriver::new(&lock_name, #connection);
47+
let config = LockConfig::from_mills(lock_name, #at_least_mills, #at_most_mills);
48+
DistLock::new(config, driver)
49+
}
50+
}
51+
}
52+
53+
#[cfg(feature = "diesel")]
54+
fn gen_lock_code(
55+
name: String,
56+
at_most_mills: i64,
57+
at_least_mills: i64,
58+
connection: ExprCall,
59+
) -> TokenStream {
60+
let name_ident = Ident::new(&format!("_{}", &name), Span::call_site());
61+
quote! {
62+
let mut #name_ident = {
63+
use ::dist_lock::core::DistLock;
64+
use ::dist_lock::core::LockConfig;
65+
use ::dist_lock::provider::diesel::DieselDriver;
66+
67+
let lock_name = #name.to_string();
68+
let driver = DieselDriver::new(&lock_name, Some("t"), #connection);
69+
let config = LockConfig::from_mills(lock_name, #at_least_mills, #at_most_mills);
70+
DistLock::new(config, driver)
71+
}
72+
}
73+
}

codegen/src/lib.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use crate::parse_args::DistLockArgs;
2+
use impl_lock::generate;
3+
use proc_macro::TokenStream;
4+
use quote::quote;
5+
use syn::parse_macro_input;
6+
use syn::ItemFn;
7+
use syn::Result;
8+
9+
mod impl_lock;
10+
mod parse_args;
11+
12+
#[proc_macro_attribute]
13+
pub fn dist_lock(head: TokenStream, body: TokenStream) -> TokenStream {
14+
let lock_args = parse_macro_input!(head as DistLockArgs);
15+
let function = parse_macro_input!(body as ItemFn);
16+
parse(lock_args, function).unwrap_or_else(to_compile_error).into()
17+
}
18+
19+
fn parse(lock_args: DistLockArgs, function: ItemFn) -> Result<proc_macro2::TokenStream> {
20+
let dist_lock = generate(lock_args)?;
21+
let fn_vis = function.vis;
22+
let fn_body = function.block;
23+
let fn_sig = function.sig;
24+
let fn_name = fn_sig.ident;
25+
let fn_generics = fn_sig.generics;
26+
let fn_inputs = fn_sig.inputs;
27+
let fn_output = fn_sig.output;
28+
Ok(quote! {
29+
#fn_vis fn #fn_name #fn_generics (#fn_inputs) #fn_output{
30+
#dist_lock;
31+
#fn_body
32+
}
33+
})
34+
}
35+
36+
fn to_compile_error(e: syn::Error) -> proc_macro2::TokenStream {
37+
e.to_compile_error()
38+
}

codegen/src/parse_args.rs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use proc_macro2::Span;
2+
use syn::parenthesized;
3+
use syn::parse::Parse;
4+
use syn::ExprCall;
5+
use syn::LitStr;
6+
use syn::Token;
7+
8+
pub(crate) struct DistLockArgs {
9+
pub(crate) name: LitStr,
10+
pub(crate) at_most: LitStr,
11+
pub(crate) at_least: Option<LitStr>,
12+
pub(crate) connection: ExprCall,
13+
}
14+
15+
impl Parse for DistLockArgs {
16+
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
17+
let mut name = None;
18+
let mut at_least = None;
19+
let mut at_most = None;
20+
let mut connection = None;
21+
while !input.is_empty() {
22+
let lookahead = input.lookahead1();
23+
if lookahead.peek(kw::name) {
24+
_ = input.parse::<kw::name>()?;
25+
_ = input.parse::<Token![=]>()?;
26+
name = Some(input.parse()?);
27+
} else if lookahead.peek(kw::at_most) {
28+
_ = input.parse::<kw::at_most>()?;
29+
_ = input.parse::<Token![=]>()?;
30+
at_most = Some(input.parse::<LitStr>()?);
31+
} else if lookahead.peek(kw::at_least) {
32+
_ = input.parse::<kw::at_least>()?;
33+
_ = input.parse::<Token![=]>()?;
34+
at_least = Some(input.parse::<LitStr>()?);
35+
} else if lookahead.peek(kw::connection) {
36+
_ = input.parse::<kw::connection>()?;
37+
let content;
38+
parenthesized!(content in input);
39+
connection = Some(content.parse::<ExprCall>()?);
40+
} else if lookahead.peek(Token![,]) {
41+
_ = input.parse::<Token![,]>()?;
42+
} else {
43+
return Err(lookahead.error());
44+
}
45+
}
46+
47+
Ok(DistLockArgs {
48+
name: name.ok_or(syn::Error::new(Span::call_site(), "lock name not found"))?,
49+
at_most: at_most.ok_or(syn::Error::new(Span::call_site(), "at_most not found"))?,
50+
at_least,
51+
connection: connection
52+
.ok_or(syn::Error::new(Span::call_site(), "connection not found"))?,
53+
})
54+
}
55+
}
56+
57+
mod kw {
58+
use syn::custom_keyword;
59+
60+
custom_keyword!(name);
61+
custom_keyword!(at_least);
62+
custom_keyword!(at_most);
63+
custom_keyword!(connection);
64+
}

rust-toolchain.toml

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

src/core.rs

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ impl LockConfig {
7777
LockConfig { name, min_lock, max_lock }
7878
}
7979

80+
pub fn from_mills(name: String, min_lock: i64, max_lock: i64) -> LockConfig {
81+
Self::new(name, Duration::milliseconds(min_lock), Duration::milliseconds(max_lock))
82+
}
83+
8084
pub fn name(&self) -> &String {
8185
&self.name
8286
}

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ pub mod core;
22
pub mod error;
33
pub mod provider;
44

5+
pub use dist_lock_codegen::dist_lock;
6+
57
#[cfg(test)]
68
mod tests {
79
use chrono::Duration;

src/provider/diesel.rs

-21
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::fmt::Display;
33
use chrono::Utc;
44
use diesel::sql_types::BigInt;
55
use diesel::sql_types::VarChar;
6-
use diesel::QueryableByName;
76
use diesel::RunQueryDsl;
87
use gethostname::gethostname;
98

@@ -19,26 +18,6 @@ use super::help::sql_stmt::update_lock_sql;
1918

2019
const LOCK_TABLE: &'static str = "dist_lock";
2120

22-
diesel::table! {
23-
dist_lock (name) {
24-
#[max_length = 64]
25-
name -> Varchar,
26-
lock_until -> Bigint,
27-
locked_at -> Bigint,
28-
#[max_length = 255]
29-
locked_by -> Varchar,
30-
}
31-
}
32-
33-
#[derive(QueryableByName)]
34-
#[diesel(table_name = crate::provider::diesel::dist_lock)]
35-
pub struct LockRecord {
36-
pub name: String,
37-
pub lock_until: i64,
38-
pub locked_at: i64,
39-
pub locked_by: String,
40-
}
41-
4221
#[derive(Debug)]
4322
pub struct DieselDriver<T> {
4423
name: String,

src/provider/help/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
#[cfg(feature = "diesel")]
12
pub(crate) mod sql_stmt;

0 commit comments

Comments
 (0)