use std::sync::Arc;
use async_trait::async_trait;
use crate::Result;
#[async_trait]
pub trait Lock: Send + Sync {
async fn acquire(&self, namespace: &str, resource_id: &str) -> Result<()>;
async fn try_acquire(&self, namespace: &str, resource_id: &str) -> Result<bool>;
async fn release(&self, namespace: &str, resource_id: &str) -> Result<bool>;
async fn acquire_with_timeout(
&self,
namespace: &str,
resource_id: &str,
timeout_ms: i32,
retry_interval_ms: u64,
) -> Result<()>;
}
pub struct LockGuard {
lock: Arc<dyn Lock>,
namespace: String,
resource_id: String,
released: bool,
}
impl LockGuard {
pub async fn acquire(
lock: Arc<dyn Lock>,
namespace: &str,
resource_id: &str,
) -> Result<Self> {
lock.acquire(namespace, resource_id).await?;
Ok(Self {
lock,
namespace: namespace.to_string(),
resource_id: resource_id.to_string(),
released: false,
})
}
pub async fn try_acquire(
lock: Arc<dyn Lock>,
namespace: &str,
resource_id: &str,
) -> Result<Option<Self>> {
if lock.try_acquire(namespace, resource_id).await? {
Ok(Some(Self {
lock,
namespace: namespace.to_string(),
resource_id: resource_id.to_string(),
released: false,
}))
} else {
Ok(None)
}
}
pub async fn release(mut self) -> Result<bool> {
self.released = true;
self.lock.release(&self.namespace, &self.resource_id).await
}
}
impl Drop for LockGuard {
fn drop(&mut self) {
if !self.released {
let lock = self.lock.clone();
let namespace = self.namespace.clone();
let resource_id = self.resource_id.clone();
let Some(handle) = tokio::runtime::Handle::try_current().ok() else {
tracing::warn!(
namespace = %namespace,
resource_id = %resource_id,
"Cannot release lock: no tokio runtime in Drop"
);
return;
};
handle.spawn(async move {
if let Err(e) = lock.release(&namespace, &resource_id).await {
tracing::error!(
namespace = %namespace,
resource_id = %resource_id,
error = %e,
"failed to release lock on drop"
);
}
});
}
}
}