use rand::distributions::Standard;
use rand::prelude::Distribution;
use rand::Rng;
use redis::{Client, Commands as _, Connection};
use redis_lock::sync::MultiResourceLock;
use std::error::Error;
use std::time::Duration;
fn transfer(
conn: &mut Connection,
from: &str,
to: &str,
amount: i64,
) -> Result<(), Box<dyn Error>> {
let from_balance: i64 = conn.get(from)?;
if from_balance >= amount {
let to_balance: i64 = conn.get(to)?;
conn.set(from, from_balance.checked_sub(amount).ok_or("underflow")?)?;
conn.set(to, to_balance.checked_add(amount).ok_or("overflow")?)?;
}
Ok(())
}
enum TransferType {
Account1ToAccount2,
Account2ToAccount3,
Account1ToAccount3,
}
#[expect(clippy::unreachable, reason = "It's unreachable and it's a test")]
impl Distribution<TransferType> for Standard {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TransferType {
match rng.gen_range(0..3) {
0 => TransferType::Account1ToAccount2,
1 => TransferType::Account2ToAccount3,
2 => TransferType::Account1ToAccount3,
_ => unreachable!(),
}
}
}
fn main() -> Result<(), Box<dyn Error>> {
let redis_url = "redis://127.0.0.1/";
let client = Client::open(redis_url)?;
let mut lock = MultiResourceLock::new(&client)?;
let mut conn = client.get_connection()?;
let mut rng = rand::thread_rng();
for _ in 0..10usize {
let amount = rng.gen_range(10..=100);
let transfer_type = rng.gen();
let (from, to, resources) = match transfer_type {
TransferType::Account1ToAccount2 => (
"account1",
"account2",
vec![String::from("account1"), String::from("account2")],
),
TransferType::Account2ToAccount3 => (
"account2",
"account3",
vec![String::from("account2"), String::from("account3")],
),
TransferType::Account1ToAccount3 => (
"account1",
"account3",
vec![String::from("account1"), String::from("account3")],
),
};
let opt = lock.lock(
&resources,
Duration::from_secs(60),
Duration::from_secs(2),
Duration::from_millis(100),
)?;
let guard = opt.ok_or("Timed out")?;
transfer(&mut conn, from, to, amount)?;
drop(guard);
}
Ok(())
}