use distributed_lock_core::error::{LockError, LockResult};
use fred::prelude::*;
use super::helper::RedLockHelper;
pub async fn release_redlock<F, Fut>(
try_release_fn: F,
clients: &[RedisClient],
acquire_results: &[bool],
) -> LockResult<()>
where
F: Fn(&RedisClient) -> Fut + Send + Sync + Clone + 'static,
Fut: std::future::Future<Output = LockResult<()>> + Send,
{
let clients_to_release: Vec<RedisClient> = acquire_results
.iter()
.enumerate()
.filter(|&(_, &success)| success)
.map(|(idx, _)| clients[idx].clone())
.collect();
if clients_to_release.is_empty() {
return Ok(()); }
let mut release_tasks: Vec<tokio::task::JoinHandle<LockResult<()>>> = Vec::new();
for client in &clients_to_release {
let client_clone = client.clone();
let try_release_fn_clone = try_release_fn.clone();
let task = tokio::spawn(async move { try_release_fn_clone(&client_clone).await });
release_tasks.push(task);
}
let mut success_count = 0;
let mut fault_count = 0;
let mut errors: Vec<LockError> = Vec::new();
let total_clients = clients_to_release.len();
for task in release_tasks {
match task.await {
Ok(Ok(())) => {
success_count += 1;
}
Ok(Err(e)) => {
fault_count += 1;
errors.push(e);
}
Err(_) => {
fault_count += 1;
}
}
}
if RedLockHelper::has_sufficient_successes(success_count, total_clients) {
Ok(())
} else if RedLockHelper::has_too_many_failures_or_faults(fault_count, total_clients) {
if errors.is_empty() {
Err(LockError::Backend(Box::new(std::io::Error::other(
"failed to release lock on majority of servers",
))))
} else {
Err(errors.into_iter().next().unwrap())
}
} else {
Ok(())
}
}