Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
//! The Glean DB can only be accessed by one process at a time, so this module offers a simple way
//! to negotiate this (allowing other processes to wait).
//!
//! TODO: Use std file locking when the MSRV is >=1.89.
use std::{
fs::{File, OpenOptions},
io::{Result, Write},
path::Path,
process,
};
pub struct SingleInstance {
lockfile: File,
}
impl SingleInstance {
pub fn acquire(path: &Path) -> Result<Self> {
let mut lockfile = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)?;
lock(&lockfile)?;
write!(&mut lockfile, "{}", process::id())?;
Ok(SingleInstance { lockfile })
}
pub fn retain_until_application_exit(self) {
std::mem::forget(self);
}
}
impl Drop for SingleInstance {
fn drop(&mut self) {
// Don't attempt to delete the file, as there are races that can't be avoided if we do.
let _ = self.lockfile.set_len(0);
if let Err(e) = unlock(&self.lockfile) {
log::error!("failed to unlock lockfile: {e}");
}
}
}
#[cfg(unix)]
mod unix {
use std::ffi::c_int;
use std::fs::File;
use std::io::{Error, Result};
use std::os::fd::AsRawFd;
const LOCK_EX: c_int = 2;
const LOCK_UN: c_int = 8;
extern "C" {
fn flock(fd: c_int, operation: c_int) -> c_int;
}
pub fn lock(file: &File) -> Result<()> {
let result = unsafe { flock(file.as_raw_fd(), LOCK_EX) };
(result == 0).then_some(()).ok_or_else(Error::last_os_error)
}
pub fn unlock(file: &File) -> Result<()> {
let result = unsafe { flock(file.as_raw_fd(), LOCK_UN) };
(result == 0).then_some(()).ok_or_else(Error::last_os_error)
}
}
#[cfg(unix)]
use unix::{lock, unlock};
#[cfg(windows)]
mod windows {
use std::ffi::{c_int, c_void};
use std::fs::File;
use std::io::{Error, Result};
use std::os::windows::io::AsRawHandle;
type HANDLE = *mut c_void;
type DWORD = u32;
type BOOL = c_int;
const LOCKFILE_EXCLUSIVE_LOCK: DWORD = 2;
#[repr(C)]
struct OVERLAPPED {
internal: usize,
internalhigh: usize,
// Omit the union of the offset fields with the `PVOID Pointer` since we don't need it.
offset: DWORD,
offsethigh: DWORD,
hevent: HANDLE,
}
impl Default for OVERLAPPED {
fn default() -> Self {
// # Safety
// A zeroed OVERLAPPED is defined as valid.
unsafe { std::mem::zeroed() }
}
}
extern "system" {
fn LockFileEx(
hfile: HANDLE,
dwflags: DWORD,
dwreserved: DWORD,
nnumberofbytestolocklow: DWORD,
nnumberofbytestolockhigh: DWORD,
lpoverlapped: *mut OVERLAPPED,
) -> BOOL;
fn UnlockFileEx(
hfile: HANDLE,
dwreserved: DWORD,
nnumberofbytestounlocklow: DWORD,
nnumberofbytestounlockhigh: DWORD,
lpoverlapped: *mut OVERLAPPED,
) -> BOOL;
}
pub fn lock(file: &File) -> Result<()> {
let mut overlapped = OVERLAPPED::default();
let result = unsafe {
LockFileEx(
file.as_raw_handle(),
LOCKFILE_EXCLUSIVE_LOCK,
0,
u32::MAX,
u32::MAX,
&mut overlapped,
)
};
(result != 0).then_some(()).ok_or_else(Error::last_os_error)
}
pub fn unlock(file: &File) -> Result<()> {
let mut overlapped = OVERLAPPED::default();
let result =
unsafe { UnlockFileEx(file.as_raw_handle(), 0, u32::MAX, u32::MAX, &mut overlapped) };
(result != 0).then_some(()).ok_or_else(Error::last_os_error)
}
}
#[cfg(windows)]
use windows::{lock, unlock};