about summary refs log tree commit diff
diff options
context:
space:
mode:
authorChristopher Berner <me@cberner.com>2024-10-18 21:08:25 -0700
committerChristopher Berner <me@cberner.com>2024-10-19 13:50:19 -0700
commite0fdaa8624ee062b38372c2987947ff2dc6f7113 (patch)
tree786b55e5fad2c27fb0402c5e557b5fea5c16d1fe
parentd2cdc76256313e8f23f585107c43badb49d5473d (diff)
downloadrust-e0fdaa8624ee062b38372c2987947ff2dc6f7113.tar.gz
rust-e0fdaa8624ee062b38372c2987947ff2dc6f7113.zip
Support lock() and lock_shared() on async IO Files
-rw-r--r--library/std/src/fs/tests.rs38
-rw-r--r--library/std/src/sys/pal/windows/fs.rs62
2 files changed, 87 insertions, 13 deletions
diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs
index fe3dc4adff0..05efed6b5df 100644
--- a/library/std/src/fs/tests.rs
+++ b/library/std/src/fs/tests.rs
@@ -284,6 +284,44 @@ fn file_lock_double_unlock() {
 }
 
 #[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
     let initial_msg = "qwer-asdf-zxcv";
diff --git a/library/std/src/sys/pal/windows/fs.rs b/library/std/src/sys/pal/windows/fs.rs
index bc728d498c3..138ac59c161 100644
--- a/library/std/src/sys/pal/windows/fs.rs
+++ b/library/std/src/sys/pal/windows/fs.rs
@@ -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> {