filelock 0.7.0

Cross-platform file locking library for Rust
Documentation

filelock

Rust

A simple file locking library for Rust. On Rust 1.89 and newer, locking uses the standard library's std::fs::File::lock API. Rust 1.71 through 1.88 use a backport of that implementation from Rust 1.97.1, preserving the same platform behavior and interoperability.

Image by Homutan, source: https://www.pixiv.net/artworks/128080460

Installation

$ cargo add filelock

Usage

Acquire an exclusive lock, and let the guard release it:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut lock = filelock::new("myfile.lock")?;
    let _guard = lock.lock()?;

    // Perform critical operations

    // Lock is automatically released when _guard goes out of scope
    Ok(())
}

filelock::new opens (and creates if missing) the lock file, and the file stays open for the lifetime of the FileLock, so repeated lock/unlock cycles reuse the same file descriptor or handle. All operations return std::io::Result.

A guard can also be released manually, which reports errors instead of ignoring them:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut lock = filelock::new("myfile.lock")?;
    let guard = lock.lock()?;

    // Perform critical operations

    guard.unlock()?;
    Ok(())
}

Non-blocking, shared, and bounded waits

try_lock attempts to lock without waiting, and lock_timeout waits with an upper bound. Shared (read) locks allow multiple holders at once, while still excluding exclusive locks:

use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut lock = filelock::new("myfile.lock")?;

    if let Some(_guard) = lock.try_lock()? {
        // The exclusive lock was acquired without waiting.
    }

    if let Some(_guard) = lock.lock_timeout(Duration::from_secs(5))? {
        // The lock was acquired within five seconds.
    }

    let _guard = lock.lock_shared()?;
    // try_lock_shared() and lock_shared_timeout() are also available.

    Ok(())
}

Owned guards

The guards returned by lock() and friends borrow the FileLock. When a guard needs to outlive the current scope, for example stored in a struct or moved into a spawned task, use the owned variants (lock_owned, lock_shared_owned, and with the tokio feature lock_owned_async / lock_shared_owned_async), which take ownership of the FileLock and return it on unlock:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let guard = filelock::new("myfile.lock")?.lock_owned()?;
    std::thread::spawn(move || {
        let _guard = guard;
        // Critical section runs on another thread.
    });
    Ok(())
}

Using an existing file

An already-open std::fs::File can be locked via FileLock::from_file, which allows customizing how the lock file is opened. While the lock is held, the underlying file is accessible through the guard's file() method, for example to store the holder's process id in the lock file:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = std::fs::OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open("myfile.lock")?;
    let mut lock = filelock::FileLock::from_file(file);
    let _guard = lock.lock()?;
    Ok(())
}

On Windows, from_file also supports handles opened for overlapped I/O.

Tokio

Enable the optional tokio feature to wait for a contended lock without blocking a Tokio runtime worker:

cargo add filelock --features tokio
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut lock = filelock::new("myfile.lock")?;
    let _guard = lock.lock_async().await?;

    // Perform async critical operations

    Ok(())
}

lock_async() and lock_shared_async() use non-blocking lock attempts and asynchronous, bounded backoff, so lock contention does not park a Tokio runtime worker. They can be used with tokio::time::timeout or tokio::select!, and cancelling the wait does not leave a blocking task running in the background. Waiting is polling-based, with the same caveats as lock_timeout: acquisition can lag the lock's release by up to 50ms, and waiters are not queued in FIFO order under sustained contention. Do not call the synchronous lock() from a Tokio runtime worker when the lock may be contended.

Platform behavior

On Linux, the BSDs, Apple platforms, Illumos, AIX, and the other Unix targets supported by the standard-library implementation, locks use flock. Solaris uses fcntl record locks covering the entire file. Windows uses LockFileEx. Other targets return std::io::ErrorKind::Unsupported from lock operations.

  • Every participant must use this locking protocol and resolve the same stable lock-file path. Do not delete, rename, or replace the lock file while participants may be running; doing so can let processes lock different underlying files.
  • Locks are advisory and associated with the opened file, not its path. Uncooperative processes can still access the lock file, and a process must not fork while holding a guard and then let both parent and child continue through the protected critical section.
  • Lock files are opened read-write and, on Unix, created with mode 0644 before applying the process umask. Cross-user locking therefore requires permissions to be arranged explicitly.
  • Interactions between file locks and ordinary reads and writes by non-lockholders are platform specific; see the std::fs::File::lock documentation for details.
  • Network filesystem behavior depends on the operating system, filesystem, mount options, and server. Validate the required semantics before using a lock file on NFS, SMB, or another remote filesystem.

Minimum supported Rust version

Rust 1.71, including when the optional tokio feature is enabled. Builds with Rust 1.89 and newer use the standard library's native file-lock API; Rust 1.71–1.88 automatically use the bundled compatibility implementation. This backend selection is internal and does not change the public API.

The compatibility implementation is derived from the Rust 1.97.1 standard library's Unix and Windows file-locking implementations, which are licensed under Apache-2.0 OR MIT. Source and license attribution is retained in the corresponding implementation files.

Documentation

See https://docs.rs/filelock/latest/filelock/.

License

Filelock is distributed by a MIT license.