#[cfg(any(test, feature = "conformance-lock"))]
pub mod conformance;
#[cfg(all(feature = "lock-memory", not(target_arch = "wasm32")))]
pub mod memory;
#[cfg(all(feature = "lock-file", not(target_arch = "wasm32")))]
pub mod file;
use async_trait::async_trait;
use std::time::Duration;
use crate::error::Result;
use crate::runtime::{MaybeSend, MaybeSendSync};
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait Locker: MaybeSendSync {
fn name(&self) -> &'static str;
async fn lock(&self, upload_id: &str, timeout: Duration) -> Result<LockGuard>;
async fn try_lock(&self, upload_id: &str) -> Result<Option<LockGuard>>;
}
#[must_use = "dropping the LockGuard immediately releases the lock"]
pub struct LockGuard {
upload_id: String,
release_fn: Option<ReleaseFn>,
}
impl LockGuard {
pub fn new(upload_id: impl Into<String>) -> Self {
Self {
upload_id: upload_id.into(),
release_fn: None,
}
}
pub fn with_release<F>(upload_id: impl Into<String>, release_fn: F) -> Self
where
F: FnOnce() + MaybeSend + 'static,
{
Self {
upload_id: upload_id.into(),
release_fn: Some(Box::new(release_fn)),
}
}
pub fn upload_id(&self) -> &str {
&self.upload_id
}
}
impl Drop for LockGuard {
fn drop(&mut self) {
if let Some(release_fn) = self.release_fn.take() {
release_fn();
}
}
}
impl std::fmt::Debug for LockGuard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LockGuard")
.field("upload_id", &self.upload_id)
.finish()
}
}
#[derive(Debug, Clone, Default)]
pub struct NoopLocker;
impl NoopLocker {
pub fn new() -> Self {
Self
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl Locker for NoopLocker {
fn name(&self) -> &'static str {
"noop"
}
async fn lock(&self, upload_id: &str, _timeout: Duration) -> Result<LockGuard> {
Ok(LockGuard::new(upload_id))
}
async fn try_lock(&self, upload_id: &str) -> Result<Option<LockGuard>> {
Ok(Some(LockGuard::new(upload_id)))
}
}
#[cfg(target_arch = "wasm32")]
type ReleaseFn = Box<dyn FnOnce()>;
#[cfg(not(target_arch = "wasm32"))]
type ReleaseFn = Box<dyn FnOnce() + Send>;
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
fn test_lock_guard_basic() {
let guard = LockGuard::new("test-upload");
assert_eq!(guard.upload_id(), "test-upload");
}
#[test]
fn test_lock_guard_with_release() {
let released = Arc::new(AtomicBool::new(false));
let released_clone = released.clone();
{
let _guard = LockGuard::with_release("test-upload", move || {
released_clone.store(true, Ordering::SeqCst);
});
assert!(!released.load(Ordering::SeqCst));
}
assert!(released.load(Ordering::SeqCst));
}
#[cfg(target_arch = "wasm32")]
#[test]
fn test_lock_guard_release_accepts_non_send_callback_in_local_mode() {
use std::cell::Cell;
use std::rc::Rc;
let released = Rc::new(Cell::new(false));
let released_for_callback = released.clone();
drop(LockGuard::with_release("upload", move || {
released_for_callback.set(true);
}));
assert!(released.get());
}
#[tokio::test]
async fn test_noop_locker() {
let locker = NoopLocker::new();
let guard = locker.lock("test", Duration::from_secs(1)).await.unwrap();
assert_eq!(guard.upload_id(), "test");
let guard2 = locker.try_lock("test").await.unwrap();
assert!(guard2.is_some());
}
}