Skip to content

Commit

Permalink
Support lock() and lock_shared() on async IO Files
Browse files Browse the repository at this point in the history
  • Loading branch information
cberner committed Oct 19, 2024
1 parent 541bda1 commit f1c9904
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 13 deletions.
38 changes: 38 additions & 0 deletions std/src/fs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,44 @@ fn file_lock_double_unlock() {
assert!(check!(f2.try_lock()));
}

#[test]
#[cfg(windows)]
fn file_lock_blocking_async() {
use crate::thread::{sleep, spawn};
const FILE_FLAG_OVERLAPPED: u32 = 0x40000000;

let tmpdir = tmpdir();
let filename = &tmpdir.join("file_lock_blocking_async.txt");
let f1 = check!(File::create(filename));
let f2 =
check!(OpenOptions::new().custom_flags(FILE_FLAG_OVERLAPPED).write(true).open(filename));

check!(f1.lock());

// Ensure that lock() is synchronous when the file is opened for asynchronous IO
let t = spawn(move || {
check!(f2.lock());
});
sleep(Duration::from_secs(1));
assert!(!t.is_finished());
check!(f1.unlock());
t.join().unwrap();

// Ensure that lock_shared() is synchronous when the file is opened for asynchronous IO
let f2 =
check!(OpenOptions::new().custom_flags(FILE_FLAG_OVERLAPPED).write(true).open(filename));
check!(f1.lock());

// Ensure that lock() is synchronous when the file is opened for asynchronous IO
let t = spawn(move || {
check!(f2.lock_shared());
});
sleep(Duration::from_secs(1));
assert!(!t.is_finished());
check!(f1.unlock());
t.join().unwrap();
}

#[test]
fn file_test_io_seek_shakedown() {
// 01234567890123
Expand Down
62 changes: 49 additions & 13 deletions std/src/sys/pal/windows/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,27 +346,63 @@ impl File {
self.fsync()
}

pub fn lock(&self) -> io::Result<()> {
cvt(unsafe {
let mut overlapped = mem::zeroed();
c::LockFileEx(
fn acquire_lock(&self, flags: c::LOCK_FILE_FLAGS) -> io::Result<()> {
unsafe {
let mut overlapped: c::OVERLAPPED = mem::zeroed();
let event = c::CreateEventW(ptr::null_mut(), c::FALSE, c::FALSE, ptr::null());
if event.is_null() {
return Err(io::Error::last_os_error());
}
overlapped.hEvent = event;
let lock_result = cvt(c::LockFileEx(
self.handle.as_raw_handle(),
c::LOCKFILE_EXCLUSIVE_LOCK,
flags,
0,
u32::MAX,
u32::MAX,
&mut overlapped,
)
})?;
Ok(())
));

let final_result = match lock_result {
Ok(_) => Ok(()),
Err(err) => {
if err.raw_os_error() == Some(c::ERROR_IO_PENDING as i32) {
// Wait for the lock to be acquired. This can happen asynchronously,
// if the file handle was opened for async IO
let wait_result = c::WaitForSingleObject(overlapped.hEvent, c::INFINITE);
if wait_result == c::WAIT_OBJECT_0 {
// Wait completed successfully, get the lock operation status
let mut bytes_transferred = 0;
cvt(c::GetOverlappedResult(
self.handle.as_raw_handle(),
&mut overlapped,
&mut bytes_transferred,
c::TRUE,
))
.map(|_| ())
} else if wait_result == c::WAIT_FAILED {
// Wait failed
Err(io::Error::last_os_error())
} else {
// WAIT_ABANDONED and WAIT_TIMEOUT should not be possible
unreachable!()
}
} else {
Err(err)
}
}
};
c::CloseHandle(overlapped.hEvent);
final_result
}
}

pub fn lock(&self) -> io::Result<()> {
self.acquire_lock(c::LOCKFILE_EXCLUSIVE_LOCK)
}

pub fn lock_shared(&self) -> io::Result<()> {
cvt(unsafe {
let mut overlapped = mem::zeroed();
c::LockFileEx(self.handle.as_raw_handle(), 0, 0, u32::MAX, u32::MAX, &mut overlapped)
})?;
Ok(())
self.acquire_lock(0)
}

pub fn try_lock(&self) -> io::Result<bool> {
Expand Down

0 comments on commit f1c9904

Please sign in to comment.