mod file;
mod memory;
pub use file::FileLockManager;
pub use memory::InMemoryLockManager;
use std::sync::Arc;
use boxlite_shared::errors::{BoxliteError, BoxliteResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct LockId(pub u32);
impl std::fmt::Display for LockId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
pub trait LockManager: Send + Sync {
fn allocate(&self) -> BoxliteResult<LockId>;
fn retrieve(&self, id: LockId) -> BoxliteResult<Arc<dyn Locker>>;
fn allocate_and_retrieve(&self, id: LockId) -> BoxliteResult<Arc<dyn Locker>>;
fn clear_all_locks(&self) -> BoxliteResult<()>;
fn free(&self, id: LockId) -> BoxliteResult<()>;
fn free_all(&self) -> BoxliteResult<()>;
fn available(&self) -> BoxliteResult<Option<u32>>;
fn allocated_count(&self) -> BoxliteResult<u32>;
}
pub trait Locker: Send + Sync {
fn id(&self) -> LockId;
fn lock(&self);
fn unlock(&self);
fn try_lock(&self) -> bool;
}
pub struct LockGuard<'a> {
lock: &'a dyn Locker,
}
impl<'a> LockGuard<'a> {
pub fn new(lock: &'a dyn Locker) -> Self {
lock.lock();
Self { lock }
}
pub fn try_new(lock: &'a dyn Locker) -> Option<Self> {
if lock.try_lock() {
Some(Self { lock })
} else {
None
}
}
}
impl Drop for LockGuard<'_> {
fn drop(&mut self) {
self.lock.unlock();
}
}
pub(crate) fn lock_exhausted() -> BoxliteError {
BoxliteError::Internal("all locks have been allocated".to_string())
}
pub(crate) fn lock_not_found(id: LockId) -> BoxliteError {
BoxliteError::NotFound(format!("lock {}", id))
}
pub(crate) fn lock_already_allocated(id: LockId) -> BoxliteError {
BoxliteError::InvalidState(format!("lock {} is already allocated", id))
}
pub(crate) fn lock_not_allocated(id: LockId) -> BoxliteError {
BoxliteError::InvalidState(format!("lock {} is not allocated", id))
}
pub(crate) fn lock_invalid(id: LockId, max: u32) -> BoxliteError {
BoxliteError::InvalidArgument(format!("lock ID {} is too large (max: {})", id, max - 1))
}
#[cfg(test)]
mod tests {
use super::*;
fn test_lock_manager(manager: &dyn LockManager) {
let id1 = manager.allocate().expect("allocate first lock");
let id2 = manager.allocate().expect("allocate second lock");
assert_ne!(id1, id2, "lock IDs should be unique");
let lock1 = manager.retrieve(id1).expect("retrieve first lock");
let lock2 = manager.retrieve(id2).expect("retrieve second lock");
assert_eq!(lock1.id(), id1);
assert_eq!(lock2.id(), id2);
lock1.lock();
lock1.unlock();
assert!(lock2.try_lock(), "try_lock should succeed");
lock2.unlock();
manager.free(id1).expect("free first lock");
manager.free(id2).expect("free second lock");
let id3 = manager.allocate().expect("allocate after free");
assert!(id3 == id1 || id3 == id2, "should reuse freed lock");
}
#[test]
fn test_in_memory_manager() {
let manager = InMemoryLockManager::new(16);
test_lock_manager(&manager);
}
#[test]
fn test_file_manager() {
let temp_dir = tempfile::tempdir().expect("create temp dir");
let lock_path = temp_dir.path().join("locks");
let manager = FileLockManager::new(&lock_path).expect("create file lock manager");
test_lock_manager(&manager);
}
#[test]
fn test_lock_guard() {
let manager = InMemoryLockManager::new(16);
let id = manager.allocate().expect("allocate");
let lock = manager.retrieve(id).expect("retrieve");
{
let _guard = LockGuard::new(lock.as_ref());
assert!(!lock.try_lock(), "should not be able to acquire held lock");
}
assert!(lock.try_lock(), "should be able to acquire released lock");
lock.unlock();
}
}