use crate::sys::{self, TryLockError};
use crate::{FileLockGuard, OwnedFileLockGuard};
use std::fs::{File, OpenOptions};
use std::io::{self, Result};
use std::path::Path;
use std::time::{Duration, Instant};
pub(crate) const INITIAL_RETRY_DELAY: Duration = Duration::from_millis(1);
pub(crate) const MAX_RETRY_DELAY: Duration = Duration::from_millis(50);
#[derive(Debug)]
pub struct FileLock {
file: File,
}
impl FileLock {
pub fn new<P: AsRef<Path>>(filename: P) -> Result<FileLock> {
let mut options = OpenOptions::new();
options.read(true).write(true).create(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o644);
}
Ok(FileLock {
file: options.open(filename)?,
})
}
pub fn from_file(file: File) -> FileLock {
FileLock { file }
}
pub fn file(&self) -> &File {
&self.file
}
pub fn into_file(self) -> File {
self.file
}
pub fn lock(&mut self) -> Result<FileLockGuard<'_>> {
self.blocking_acquire(sys::lock)?;
Ok(FileLockGuard::new(self))
}
pub fn lock_shared(&mut self) -> Result<FileLockGuard<'_>> {
self.blocking_acquire(sys::lock_shared)?;
Ok(FileLockGuard::new(self))
}
pub fn lock_owned(self) -> Result<OwnedFileLockGuard> {
self.blocking_acquire(sys::lock)?;
Ok(OwnedFileLockGuard::new(self))
}
pub fn lock_shared_owned(self) -> Result<OwnedFileLockGuard> {
self.blocking_acquire(sys::lock_shared)?;
Ok(OwnedFileLockGuard::new(self))
}
pub fn lock_timeout(&mut self, timeout: Duration) -> Result<Option<FileLockGuard<'_>>> {
if self.wait_timeout(sys::try_lock, timeout)? {
Ok(Some(FileLockGuard::new(self)))
} else {
Ok(None)
}
}
pub fn lock_shared_timeout(&mut self, timeout: Duration) -> Result<Option<FileLockGuard<'_>>> {
if self.wait_timeout(sys::try_lock_shared, timeout)? {
Ok(Some(FileLockGuard::new(self)))
} else {
Ok(None)
}
}
pub fn try_lock(&mut self) -> Result<Option<FileLockGuard<'_>>> {
if self.try_acquire(sys::try_lock)? {
Ok(Some(FileLockGuard::new(self)))
} else {
Ok(None)
}
}
pub fn try_lock_shared(&mut self) -> Result<Option<FileLockGuard<'_>>> {
if self.try_acquire(sys::try_lock_shared)? {
Ok(Some(FileLockGuard::new(self)))
} else {
Ok(None)
}
}
fn blocking_acquire(&self, lock: fn(&File) -> io::Result<()>) -> Result<()> {
loop {
match lock(&self.file) {
Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
result => return result,
}
}
}
fn wait_timeout(
&self,
try_lock: fn(&File) -> std::result::Result<(), TryLockError>,
timeout: Duration,
) -> Result<bool> {
let deadline = Instant::now().checked_add(timeout);
let mut retry_delay = INITIAL_RETRY_DELAY;
loop {
if self.try_acquire(try_lock)? {
return Ok(true);
}
let mut sleep_for = retry_delay;
if let Some(deadline) = deadline {
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
return Ok(false);
}
sleep_for = sleep_for.min(remaining);
}
std::thread::sleep(sleep_for);
retry_delay = retry_delay.saturating_mul(2).min(MAX_RETRY_DELAY);
}
}
pub(crate) fn try_acquire(
&self,
try_lock: fn(&File) -> std::result::Result<(), TryLockError>,
) -> Result<bool> {
loop {
match try_lock(&self.file) {
Ok(()) => return Ok(true),
Err(TryLockError::WouldBlock) => return Ok(false),
Err(TryLockError::Error(error)) if error.kind() == io::ErrorKind::Interrupted => {
continue;
}
Err(TryLockError::Error(error)) => return Err(error),
}
}
}
pub(crate) fn unlock(&mut self) -> Result<()> {
sys::unlock(&self.file)
}
}