#[cfg(not(target_arch = "wasm32"))]
mod imp {
use crate::constants::LOCK_FILENAME;
use crate::error::{RayError, Result};
use std::fs::{File, OpenOptions};
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockType {
Shared,
Exclusive,
}
pub struct FileLock {
handle: LockHandle,
lock_type: LockType,
}
impl FileLock {
pub fn acquire<P: AsRef<Path>>(db_path: P, lock_type: LockType) -> Result<Self> {
let lock_path = db_path.as_ref().join(LOCK_FILENAME);
let handle = match lock_type {
LockType::Shared => LockHandle::shared(&lock_path)?,
LockType::Exclusive => LockHandle::exclusive(&lock_path)?,
};
Ok(Self { handle, lock_type })
}
pub fn lock_type(&self) -> LockType {
self.lock_type
}
pub fn is_exclusive(&self) -> bool {
self.lock_type == LockType::Exclusive
}
}
pub struct LockHandle {
file: File,
exclusive: bool,
}
impl LockHandle {
pub fn exclusive(path: impl AsRef<Path>) -> Result<Self> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(path.as_ref())
.map_err(|e| RayError::LockFailed(format!("Failed to open lock file: {e}")))?;
fs2::FileExt::lock_exclusive(&file)
.map_err(|e| RayError::LockFailed(format!("Failed to acquire exclusive lock: {e}")))?;
Ok(Self {
file,
exclusive: true,
})
}
pub fn shared(path: impl AsRef<Path>) -> Result<Self> {
let file = OpenOptions::new()
.read(true)
.write(true) .create(true)
.truncate(false)
.open(path.as_ref())
.map_err(|e| RayError::LockFailed(format!("Failed to open lock file: {e}")))?;
fs2::FileExt::lock_shared(&file)
.map_err(|e| RayError::LockFailed(format!("Failed to acquire shared lock: {e}")))?;
Ok(Self {
file,
exclusive: false,
})
}
pub fn try_exclusive(path: impl AsRef<Path>) -> Result<Option<Self>> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(path.as_ref())
.map_err(|e| RayError::LockFailed(format!("Failed to open lock file: {e}")))?;
match fs2::FileExt::try_lock_exclusive(&file) {
Ok(()) => Ok(Some(Self {
file,
exclusive: true,
})),
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None),
Err(e) => Err(RayError::LockFailed(format!(
"Failed to try exclusive lock: {e}"
))),
}
}
pub fn try_shared(path: impl AsRef<Path>) -> Result<Option<Self>> {
let file = OpenOptions::new()
.read(true)
.write(true) .create(true)
.truncate(false)
.open(path.as_ref())
.map_err(|e| RayError::LockFailed(format!("Failed to open lock file: {e}")))?;
match fs2::FileExt::try_lock_shared(&file) {
Ok(()) => Ok(Some(Self {
file,
exclusive: false,
})),
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None),
Err(e) => Err(RayError::LockFailed(format!(
"Failed to try shared lock: {e}"
))),
}
}
pub fn is_exclusive(&self) -> bool {
self.exclusive
}
pub fn release(self) -> Result<()> {
fs2::FileExt::unlock(&self.file)
.map_err(|e| RayError::LockFailed(format!("Failed to release lock: {e}")))
}
}
impl Drop for LockHandle {
fn drop(&mut self) {
let _ = fs2::FileExt::unlock(&self.file);
}
}
pub fn lock_exclusive(path: impl AsRef<Path>) -> Result<LockHandle> {
LockHandle::exclusive(path)
}
pub fn lock_shared(path: impl AsRef<Path>) -> Result<LockHandle> {
LockHandle::shared(path)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_exclusive_lock() {
let dir = tempdir().unwrap();
let lock_path = dir.path().join("test.lock");
let lock = LockHandle::exclusive(&lock_path).unwrap();
assert!(lock.is_exclusive());
let result = LockHandle::try_exclusive(&lock_path).unwrap();
assert!(result.is_none());
lock.release().unwrap();
let lock2 = LockHandle::try_exclusive(&lock_path).unwrap();
assert!(lock2.is_some());
}
#[test]
fn test_shared_lock() {
let dir = tempdir().unwrap();
let lock_path = dir.path().join("test_shared.lock");
std::fs::write(&lock_path, b"").unwrap();
let lock1 = LockHandle::shared(&lock_path).unwrap();
assert!(!lock1.is_exclusive());
drop(lock1);
let lock2 = LockHandle::try_shared(&lock_path).unwrap();
assert!(lock2.is_some());
}
#[test]
fn test_exclusive_blocks_shared() {
let dir = tempdir().unwrap();
let lock_path = dir.path().join("test_blocks.lock");
std::fs::write(&lock_path, b"").unwrap();
let _exclusive = LockHandle::exclusive(&lock_path).unwrap();
assert!(_exclusive.is_exclusive());
}
}
}
#[cfg(target_arch = "wasm32")]
mod imp {
use crate::error::Result;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockType {
Shared,
Exclusive,
}
pub struct FileLock {
lock_type: LockType,
}
impl FileLock {
pub fn acquire<P: AsRef<Path>>(_db_path: P, lock_type: LockType) -> Result<Self> {
Ok(Self { lock_type })
}
pub fn lock_type(&self) -> LockType {
self.lock_type
}
pub fn is_exclusive(&self) -> bool {
self.lock_type == LockType::Exclusive
}
}
pub struct LockHandle {
exclusive: bool,
}
impl LockHandle {
pub fn exclusive(_path: impl AsRef<Path>) -> Result<Self> {
Ok(Self { exclusive: true })
}
pub fn shared(_path: impl AsRef<Path>) -> Result<Self> {
Ok(Self { exclusive: false })
}
pub fn try_exclusive(_path: impl AsRef<Path>) -> Result<Option<Self>> {
Ok(Some(Self { exclusive: true }))
}
pub fn try_shared(_path: impl AsRef<Path>) -> Result<Option<Self>> {
Ok(Some(Self { exclusive: false }))
}
pub fn is_exclusive(&self) -> bool {
self.exclusive
}
pub fn release(self) -> Result<()> {
Ok(())
}
}
pub fn lock_exclusive(path: impl AsRef<Path>) -> Result<LockHandle> {
LockHandle::exclusive(path)
}
pub fn lock_shared(path: impl AsRef<Path>) -> Result<LockHandle> {
LockHandle::shared(path)
}
}
pub use imp::*;