Skip to content

Commit f1c9904

Browse files
committed
Support lock() and lock_shared() on async IO Files
1 parent 541bda1 commit f1c9904

File tree

2 files changed

+87
-13
lines changed

2 files changed

+87
-13
lines changed

std/src/fs/tests.rs

+38
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,44 @@ fn file_lock_double_unlock() {
283283
assert!(check!(f2.try_lock()));
284284
}
285285

286+
#[test]
287+
#[cfg(windows)]
288+
fn file_lock_blocking_async() {
289+
use crate::thread::{sleep, spawn};
290+
const FILE_FLAG_OVERLAPPED: u32 = 0x40000000;
291+
292+
let tmpdir = tmpdir();
293+
let filename = &tmpdir.join("file_lock_blocking_async.txt");
294+
let f1 = check!(File::create(filename));
295+
let f2 =
296+
check!(OpenOptions::new().custom_flags(FILE_FLAG_OVERLAPPED).write(true).open(filename));
297+
298+
check!(f1.lock());
299+
300+
// Ensure that lock() is synchronous when the file is opened for asynchronous IO
301+
let t = spawn(move || {
302+
check!(f2.lock());
303+
});
304+
sleep(Duration::from_secs(1));
305+
assert!(!t.is_finished());
306+
check!(f1.unlock());
307+
t.join().unwrap();
308+
309+
// Ensure that lock_shared() is synchronous when the file is opened for asynchronous IO
310+
let f2 =
311+
check!(OpenOptions::new().custom_flags(FILE_FLAG_OVERLAPPED).write(true).open(filename));
312+
check!(f1.lock());
313+
314+
// Ensure that lock() is synchronous when the file is opened for asynchronous IO
315+
let t = spawn(move || {
316+
check!(f2.lock_shared());
317+
});
318+
sleep(Duration::from_secs(1));
319+
assert!(!t.is_finished());
320+
check!(f1.unlock());
321+
t.join().unwrap();
322+
}
323+
286324
#[test]
287325
fn file_test_io_seek_shakedown() {
288326
// 01234567890123

std/src/sys/pal/windows/fs.rs

+49-13
Original file line numberDiff line numberDiff line change
@@ -346,27 +346,63 @@ impl File {
346346
self.fsync()
347347
}
348348

349-
pub fn lock(&self) -> io::Result<()> {
350-
cvt(unsafe {
351-
let mut overlapped = mem::zeroed();
352-
c::LockFileEx(
349+
fn acquire_lock(&self, flags: c::LOCK_FILE_FLAGS) -> io::Result<()> {
350+
unsafe {
351+
let mut overlapped: c::OVERLAPPED = mem::zeroed();
352+
let event = c::CreateEventW(ptr::null_mut(), c::FALSE, c::FALSE, ptr::null());
353+
if event.is_null() {
354+
return Err(io::Error::last_os_error());
355+
}
356+
overlapped.hEvent = event;
357+
let lock_result = cvt(c::LockFileEx(
353358
self.handle.as_raw_handle(),
354-
c::LOCKFILE_EXCLUSIVE_LOCK,
359+
flags,
355360
0,
356361
u32::MAX,
357362
u32::MAX,
358363
&mut overlapped,
359-
)
360-
})?;
361-
Ok(())
364+
));
365+
366+
let final_result = match lock_result {
367+
Ok(_) => Ok(()),
368+
Err(err) => {
369+
if err.raw_os_error() == Some(c::ERROR_IO_PENDING as i32) {
370+
// Wait for the lock to be acquired. This can happen asynchronously,
371+
// if the file handle was opened for async IO
372+
let wait_result = c::WaitForSingleObject(overlapped.hEvent, c::INFINITE);
373+
if wait_result == c::WAIT_OBJECT_0 {
374+
// Wait completed successfully, get the lock operation status
375+
let mut bytes_transferred = 0;
376+
cvt(c::GetOverlappedResult(
377+
self.handle.as_raw_handle(),
378+
&mut overlapped,
379+
&mut bytes_transferred,
380+
c::TRUE,
381+
))
382+
.map(|_| ())
383+
} else if wait_result == c::WAIT_FAILED {
384+
// Wait failed
385+
Err(io::Error::last_os_error())
386+
} else {
387+
// WAIT_ABANDONED and WAIT_TIMEOUT should not be possible
388+
unreachable!()
389+
}
390+
} else {
391+
Err(err)
392+
}
393+
}
394+
};
395+
c::CloseHandle(overlapped.hEvent);
396+
final_result
397+
}
398+
}
399+
400+
pub fn lock(&self) -> io::Result<()> {
401+
self.acquire_lock(c::LOCKFILE_EXCLUSIVE_LOCK)
362402
}
363403

364404
pub fn lock_shared(&self) -> io::Result<()> {
365-
cvt(unsafe {
366-
let mut overlapped = mem::zeroed();
367-
c::LockFileEx(self.handle.as_raw_handle(), 0, 0, u32::MAX, u32::MAX, &mut overlapped)
368-
})?;
369-
Ok(())
405+
self.acquire_lock(0)
370406
}
371407

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

0 commit comments

Comments
 (0)