Rust File Locking
A file lock is a claim one program makes on a file to ask other programs to wait. Two programs can open the same file.
Take the lock
The lock belongs to the open handle. It does not belong to the path:
let file = File::open("data.txt")?;
file.lock()?;
println!("lock held");Ask without waiting
A second handle to the same file is a second claim. try_lock answers at once:
let first = File::open("data.txt")?;
first.lock()?;
let second = OpenOptions::new().read(true).write(true).open("data.txt")?;
match second.try_lock() {
Ok(()) => println!("second got the lock"),
Err(_) => println!("second must wait"),
}Let go, and the answer changes
unlock() frees the file. The same try_lock now succeeds:
first.unlock()?;
match second.try_lock() {
Ok(()) => println!("now second got it"),
Err(_) => println!("still waiting"),
}Readers can share
Shared locks coexist. Two readers can hold the file at the same time:
let a = File::open("data.txt")?;
let b = File::open("data.txt")?;
a.lock_shared()?;
b.lock_shared()?;
println!("two readers hold it");A writer still waits
Shared locks block an exclusive claim. The writer must wait for every reader:
let c = OpenOptions::new().read(true).write(true).open("data.txt")?;
println!("writer blocked = {}", c.try_lock().is_err());Example
use std::fs::{self, OpenOptions};
use std::io::Write;
fn main() {
let path = "visitors.txt";
let _ = fs::remove_file(path);
let mut a = OpenOptions::new().create(true).append(true).open(path).expect("open A");
a.lock().expect("A lock");
writeln!(a, "A was here").expect("A write");
println!("A holds the lock");
let mut b = OpenOptions::new().append(true).open(path).expect("open B");
match b.try_lock() {
Ok(()) => println!("B got in"),
Err(_) => println!("B must wait"),
}
a.unlock().expect("A unlock");
b.lock().expect("B lock");
writeln!(b, "B was here").expect("B write");
b.unlock().expect("B unlock");
print!("{}", fs::read_to_string(path).expect("read back"));
fs::remove_file(path).expect("could not clean up");
}Key points
- Use file.lock() to take the exclusive lock: the call waits until the file is free
- Use file.try_lock() to ask without waiting: it fails with TryLockError::WouldBlock when another holder has the lock
- Use file.lock_shared() when many readers may hold the file together: an exclusive lock still waits
- Use file.unlock() to let go early: dropping the File handle lets go too
- A lock guards the file, not the data inside it: it works only when every program agrees to ask first
- A file lock lets one program claim a file so other programs wait instead of writing at the same time.
In this example, two handles play two workers. They append to one visitors file. A takes the lock and writes its line first. B asks with try_lock and must wait. A unlocks. B then takes the lock and writes its line. The read-back shows both lines in order. No line overwrote the other. That is the whole job of a lock.
Note
Locks are advice, not a wall.
A program that never asks can still open and change the file.
Rust added these calls to std in version 1.89.
Older code used the fs2 crate.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
try_lock fails with WouldBlock. What does that tell you?