about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-11-06 09:57:45 +0000
committerbors <bors@rust-lang.org>2022-11-06 09:57:45 +0000
commit4828edf39cf4fb74fd1c8dd5102d050df11dd867 (patch)
tree59de30975f35f26a6a6856ba5bb52c5af2feaf4c
parent42e450d6a0b5442085466bd0a93b2a77190ae0ce (diff)
parent2eb07a05a3fa1e604f4243ddd5659f294d6b69b9 (diff)
downloadrust-4828edf39cf4fb74fd1c8dd5102d050df11dd867.tar.gz
rust-4828edf39cf4fb74fd1c8dd5102d050df11dd867.zip
Auto merge of #2638 - DrMeepster:windows-condvars, r=RalfJung
Implement condvars for Windows

Adds 3 shims for Windows: `SleepConditionVariableSRW`, `WakeConditionVariable`, `WakeAllConditionVariable` to add support for condvars (which fixes #2628).

Salvaged from what was removed from #2231
-rw-r--r--src/tools/miri/src/concurrency/sync.rs24
-rw-r--r--src/tools/miri/src/shims/unix/sync.rs21
-rw-r--r--src/tools/miri/src/shims/windows/foreign_items.rs19
-rw-r--r--src/tools/miri/src/shims/windows/sync.rs161
-rw-r--r--src/tools/miri/tests/pass/concurrency/sync.rs20
-rw-r--r--src/tools/miri/tests/pass/concurrency/sync_nopreempt.rs1
-rw-r--r--src/tools/miri/tests/pass/concurrency/windows_condvar_shared.rs227
-rw-r--r--src/tools/miri/tests/pass/concurrency/windows_condvar_shared.stdout60
-rw-r--r--src/tools/miri/tests/pass/panic/concurrent-panic.rs1
9 files changed, 504 insertions, 30 deletions
diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs
index e76610e7302..ba5ae852c5a 100644
--- a/src/tools/miri/src/concurrency/sync.rs
+++ b/src/tools/miri/src/concurrency/sync.rs
@@ -116,13 +116,25 @@ struct RwLock {
 
 declare_id!(CondvarId);
 
+#[derive(Debug, Copy, Clone)]
+pub enum RwLockMode {
+    Read,
+    Write,
+}
+
+#[derive(Debug)]
+pub enum CondvarLock {
+    Mutex(MutexId),
+    RwLock { id: RwLockId, mode: RwLockMode },
+}
+
 /// A thread waiting on a conditional variable.
 #[derive(Debug)]
 struct CondvarWaiter {
     /// The thread that is waiting on this variable.
     thread: ThreadId,
-    /// The mutex on which the thread is waiting.
-    mutex: MutexId,
+    /// The mutex or rwlock on which the thread is waiting.
+    lock: CondvarLock,
 }
 
 /// The conditional variable state.
@@ -569,16 +581,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     }
 
     /// Mark that the thread is waiting on the conditional variable.
-    fn condvar_wait(&mut self, id: CondvarId, thread: ThreadId, mutex: MutexId) {
+    fn condvar_wait(&mut self, id: CondvarId, thread: ThreadId, lock: CondvarLock) {
         let this = self.eval_context_mut();
         let waiters = &mut this.machine.threads.sync.condvars[id].waiters;
         assert!(waiters.iter().all(|waiter| waiter.thread != thread), "thread is already waiting");
-        waiters.push_back(CondvarWaiter { thread, mutex });
+        waiters.push_back(CondvarWaiter { thread, lock });
     }
 
     /// Wake up some thread (if there is any) sleeping on the conditional
     /// variable.
-    fn condvar_signal(&mut self, id: CondvarId) -> Option<(ThreadId, MutexId)> {
+    fn condvar_signal(&mut self, id: CondvarId) -> Option<(ThreadId, CondvarLock)> {
         let this = self.eval_context_mut();
         let current_thread = this.get_active_thread();
         let condvar = &mut this.machine.threads.sync.condvars[id];
@@ -592,7 +604,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             if let Some(data_race) = data_race {
                 data_race.validate_lock_acquire(&condvar.data_race, waiter.thread);
             }
-            (waiter.thread, waiter.mutex)
+            (waiter.thread, waiter.lock)
         })
     }
 
diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs
index fcb00692079..a7275646847 100644
--- a/src/tools/miri/src/shims/unix/sync.rs
+++ b/src/tools/miri/src/shims/unix/sync.rs
@@ -3,6 +3,7 @@ use std::time::SystemTime;
 use rustc_hir::LangItem;
 use rustc_middle::ty::{layout::TyAndLayout, query::TyCtxtAt, Ty};
 
+use crate::concurrency::sync::CondvarLock;
 use crate::concurrency::thread::{MachineCallback, Time};
 use crate::*;
 
@@ -696,8 +697,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     fn pthread_cond_signal(&mut self, cond_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
         let id = this.condvar_get_or_create_id(cond_op, CONDVAR_ID_OFFSET)?;
-        if let Some((thread, mutex)) = this.condvar_signal(id) {
-            post_cond_signal(this, thread, mutex)?;
+        if let Some((thread, lock)) = this.condvar_signal(id) {
+            if let CondvarLock::Mutex(mutex) = lock {
+                post_cond_signal(this, thread, mutex)?;
+            } else {
+                panic!("condvar should not have an rwlock on unix");
+            }
         }
 
         Ok(0)
@@ -710,8 +715,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         let this = self.eval_context_mut();
         let id = this.condvar_get_or_create_id(cond_op, CONDVAR_ID_OFFSET)?;
 
-        while let Some((thread, mutex)) = this.condvar_signal(id) {
-            post_cond_signal(this, thread, mutex)?;
+        while let Some((thread, lock)) = this.condvar_signal(id) {
+            if let CondvarLock::Mutex(mutex) = lock {
+                post_cond_signal(this, thread, mutex)?;
+            } else {
+                panic!("condvar should not have an rwlock on unix");
+            }
         }
 
         Ok(0)
@@ -729,7 +738,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         let active_thread = this.get_active_thread();
 
         release_cond_mutex_and_block(this, active_thread, mutex_id)?;
-        this.condvar_wait(id, active_thread, mutex_id);
+        this.condvar_wait(id, active_thread, CondvarLock::Mutex(mutex_id));
 
         Ok(0)
     }
@@ -768,7 +777,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
         };
 
         release_cond_mutex_and_block(this, active_thread, mutex_id)?;
-        this.condvar_wait(id, active_thread, mutex_id);
+        this.condvar_wait(id, active_thread, CondvarLock::Mutex(mutex_id));
 
         // We return success for now and override it in the timeout callback.
         this.write_scalar(Scalar::from_i32(0), dest)?;
diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs
index 2a34a3a47bb..e16749c986b 100644
--- a/src/tools/miri/src/shims/windows/foreign_items.rs
+++ b/src/tools/miri/src/shims/windows/foreign_items.rs
@@ -273,6 +273,25 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 let result = this.InitOnceComplete(ptr, flags, context)?;
                 this.write_scalar(result, dest)?;
             }
+            "SleepConditionVariableSRW" => {
+                let [condvar, lock, timeout, flags] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                let result = this.SleepConditionVariableSRW(condvar, lock, timeout, flags, dest)?;
+                this.write_scalar(result, dest)?;
+            }
+            "WakeConditionVariable" => {
+                let [condvar] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                this.WakeConditionVariable(condvar)?;
+            }
+            "WakeAllConditionVariable" => {
+                let [condvar] =
+                    this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?;
+
+                this.WakeAllConditionVariable(condvar)?;
+            }
 
             // Dynamic symbol loading
             "GetProcAddress" => {
diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs
index 098804626f2..8f414d98dba 100644
--- a/src/tools/miri/src/shims/windows/sync.rs
+++ b/src/tools/miri/src/shims/windows/sync.rs
@@ -3,11 +3,45 @@ use std::time::Duration;
 use rustc_target::abi::Size;
 
 use crate::concurrency::init_once::InitOnceStatus;
+use crate::concurrency::sync::{CondvarLock, RwLockMode};
 use crate::concurrency::thread::MachineCallback;
 use crate::*;
 
 const SRWLOCK_ID_OFFSET: u64 = 0;
 const INIT_ONCE_ID_OFFSET: u64 = 0;
+const CONDVAR_ID_OFFSET: u64 = 0;
+
+impl<'mir, 'tcx> EvalContextExtPriv<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    /// Try to reacquire the lock associated with the condition variable after we
+    /// were signaled.
+    fn reacquire_cond_lock(
+        &mut self,
+        thread: ThreadId,
+        lock: RwLockId,
+        mode: RwLockMode,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        this.unblock_thread(thread);
+
+        match mode {
+            RwLockMode::Read =>
+                if this.rwlock_is_write_locked(lock) {
+                    this.rwlock_enqueue_and_block_reader(lock, thread);
+                } else {
+                    this.rwlock_reader_lock(lock, thread);
+                },
+            RwLockMode::Write =>
+                if this.rwlock_is_locked(lock) {
+                    this.rwlock_enqueue_and_block_writer(lock, thread);
+                } else {
+                    this.rwlock_writer_lock(lock, thread);
+                },
+        }
+
+        Ok(())
+    }
+}
 
 impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
 #[allow(non_snake_case)]
@@ -327,4 +361,131 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
 
         Ok(())
     }
+
+    fn SleepConditionVariableSRW(
+        &mut self,
+        condvar_op: &OpTy<'tcx, Provenance>,
+        lock_op: &OpTy<'tcx, Provenance>,
+        timeout_op: &OpTy<'tcx, Provenance>,
+        flags_op: &OpTy<'tcx, Provenance>,
+        dest: &PlaceTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+
+        let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?;
+        let lock_id = this.rwlock_get_or_create_id(lock_op, SRWLOCK_ID_OFFSET)?;
+        let timeout_ms = this.read_scalar(timeout_op)?.to_u32()?;
+        let flags = this.read_scalar(flags_op)?.to_u32()?;
+
+        let timeout_time = if timeout_ms == this.eval_windows("c", "INFINITE")?.to_u32()? {
+            None
+        } else {
+            let duration = Duration::from_millis(timeout_ms.into());
+            Some(this.machine.clock.now().checked_add(duration).unwrap())
+        };
+
+        let shared_mode = 0x1; // CONDITION_VARIABLE_LOCKMODE_SHARED is not in std
+        let mode = if flags == 0 {
+            RwLockMode::Write
+        } else if flags == shared_mode {
+            RwLockMode::Read
+        } else {
+            throw_unsup_format!("unsupported `Flags` {flags} in `SleepConditionVariableSRW`");
+        };
+
+        let active_thread = this.get_active_thread();
+
+        let was_locked = match mode {
+            RwLockMode::Read => this.rwlock_reader_unlock(lock_id, active_thread),
+            RwLockMode::Write => this.rwlock_writer_unlock(lock_id, active_thread),
+        };
+
+        if !was_locked {
+            throw_ub_format!(
+                "calling SleepConditionVariableSRW with an SRWLock that is not locked by the current thread"
+            );
+        }
+
+        this.block_thread(active_thread);
+        this.condvar_wait(condvar_id, active_thread, CondvarLock::RwLock { id: lock_id, mode });
+
+        if let Some(timeout_time) = timeout_time {
+            struct Callback<'tcx> {
+                thread: ThreadId,
+                condvar_id: CondvarId,
+                lock_id: RwLockId,
+                mode: RwLockMode,
+                dest: PlaceTy<'tcx, Provenance>,
+            }
+
+            impl<'tcx> VisitTags for Callback<'tcx> {
+                fn visit_tags(&self, visit: &mut dyn FnMut(SbTag)) {
+                    let Callback { thread: _, condvar_id: _, lock_id: _, mode: _, dest } = self;
+                    dest.visit_tags(visit);
+                }
+            }
+
+            impl<'mir, 'tcx: 'mir> MachineCallback<'mir, 'tcx> for Callback<'tcx> {
+                fn call(&self, this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx> {
+                    this.reacquire_cond_lock(self.thread, self.lock_id, self.mode)?;
+
+                    this.condvar_remove_waiter(self.condvar_id, self.thread);
+
+                    let error_timeout = this.eval_windows("c", "ERROR_TIMEOUT")?;
+                    this.set_last_error(error_timeout)?;
+                    this.write_scalar(this.eval_windows("c", "FALSE")?, &self.dest)?;
+                    Ok(())
+                }
+            }
+
+            this.register_timeout_callback(
+                active_thread,
+                Time::Monotonic(timeout_time),
+                Box::new(Callback {
+                    thread: active_thread,
+                    condvar_id,
+                    lock_id,
+                    mode,
+                    dest: dest.clone(),
+                }),
+            );
+        }
+
+        this.eval_windows("c", "TRUE")
+    }
+
+    fn WakeConditionVariable(&mut self, condvar_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?;
+
+        if let Some((thread, lock)) = this.condvar_signal(condvar_id) {
+            if let CondvarLock::RwLock { id, mode } = lock {
+                this.reacquire_cond_lock(thread, id, mode)?;
+                this.unregister_timeout_callback_if_exists(thread);
+            } else {
+                panic!("mutexes should not exist on windows");
+            }
+        }
+
+        Ok(())
+    }
+
+    fn WakeAllConditionVariable(
+        &mut self,
+        condvar_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        let condvar_id = this.condvar_get_or_create_id(condvar_op, CONDVAR_ID_OFFSET)?;
+
+        while let Some((thread, lock)) = this.condvar_signal(condvar_id) {
+            if let CondvarLock::RwLock { id, mode } = lock {
+                this.reacquire_cond_lock(thread, id, mode)?;
+                this.unregister_timeout_callback_if_exists(thread);
+            } else {
+                panic!("mutexes should not exist on windows");
+            }
+        }
+
+        Ok(())
+    }
 }
diff --git a/src/tools/miri/tests/pass/concurrency/sync.rs b/src/tools/miri/tests/pass/concurrency/sync.rs
index b1518a49fbb..19ea6c130bd 100644
--- a/src/tools/miri/tests/pass/concurrency/sync.rs
+++ b/src/tools/miri/tests/pass/concurrency/sync.rs
@@ -230,20 +230,8 @@ fn main() {
     check_once();
     park_timeout();
     park_unpark();
-
-    if !cfg!(windows) {
-        // ignore-target-windows: Condvars on Windows are not supported yet
-        check_barriers();
-        check_conditional_variables_notify_one();
-        check_conditional_variables_timed_wait_timeout();
-        check_conditional_variables_timed_wait_notimeout();
-    } else {
-        // We need to fake the same output...
-        for _ in 0..10 {
-            println!("before wait");
-        }
-        for _ in 0..10 {
-            println!("after wait");
-        }
-    }
+    check_barriers();
+    check_conditional_variables_notify_one();
+    check_conditional_variables_timed_wait_timeout();
+    check_conditional_variables_timed_wait_notimeout();
 }
diff --git a/src/tools/miri/tests/pass/concurrency/sync_nopreempt.rs b/src/tools/miri/tests/pass/concurrency/sync_nopreempt.rs
index 55206f4bfc5..c6cff038f81 100644
--- a/src/tools/miri/tests/pass/concurrency/sync_nopreempt.rs
+++ b/src/tools/miri/tests/pass/concurrency/sync_nopreempt.rs
@@ -1,4 +1,3 @@
-//@ignore-target-windows: Condvars on Windows are not supported yet.
 // We are making scheduler assumptions here.
 //@compile-flags: -Zmiri-strict-provenance -Zmiri-preemption-rate=0
 
diff --git a/src/tools/miri/tests/pass/concurrency/windows_condvar_shared.rs b/src/tools/miri/tests/pass/concurrency/windows_condvar_shared.rs
new file mode 100644
index 00000000000..d89320bfe59
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/windows_condvar_shared.rs
@@ -0,0 +1,227 @@
+//@only-target-windows: Uses win32 api functions
+// We are making scheduler assumptions here.
+//@compile-flags: -Zmiri-preemption-rate=0
+
+use std::ffi::c_void;
+use std::ptr::null_mut;
+use std::thread;
+
+#[derive(Copy, Clone)]
+struct SendPtr<T>(*mut T);
+
+unsafe impl<T> Send for SendPtr<T> {}
+
+extern "system" {
+    fn SleepConditionVariableSRW(
+        condvar: *mut *mut c_void,
+        lock: *mut *mut c_void,
+        timeout: u32,
+        flags: u32,
+    ) -> i32;
+    fn WakeAllConditionVariable(condvar: *mut *mut c_void);
+
+    fn AcquireSRWLockExclusive(lock: *mut *mut c_void);
+    fn AcquireSRWLockShared(lock: *mut *mut c_void);
+    fn ReleaseSRWLockExclusive(lock: *mut *mut c_void);
+    fn ReleaseSRWLockShared(lock: *mut *mut c_void);
+}
+
+const CONDITION_VARIABLE_LOCKMODE_SHARED: u32 = 1;
+const INFINITE: u32 = u32::MAX;
+
+/// threads should be able to reacquire the lock while it is locked by multiple other threads in shared mode
+fn all_shared() {
+    println!("all_shared");
+
+    let mut lock = null_mut();
+    let mut condvar = null_mut();
+
+    let lock_ptr = SendPtr(&mut lock);
+    let condvar_ptr = SendPtr(&mut condvar);
+
+    let mut handles = Vec::with_capacity(10);
+
+    // waiters
+    for i in 0..5 {
+        handles.push(thread::spawn(move || {
+            unsafe {
+                AcquireSRWLockShared(lock_ptr.0);
+            }
+            println!("exclusive waiter {i} locked");
+
+            let r = unsafe {
+                SleepConditionVariableSRW(
+                    condvar_ptr.0,
+                    lock_ptr.0,
+                    INFINITE,
+                    CONDITION_VARIABLE_LOCKMODE_SHARED,
+                )
+            };
+            assert_ne!(r, 0);
+
+            println!("exclusive waiter {i} reacquired lock");
+
+            // unlocking is unnecessary because the lock is never used again
+        }));
+    }
+
+    // ensures each waiter is waiting by this point
+    thread::yield_now();
+
+    // readers
+    for i in 0..5 {
+        handles.push(thread::spawn(move || {
+            unsafe {
+                AcquireSRWLockShared(lock_ptr.0);
+            }
+            println!("reader {i} locked");
+
+            // switch to next reader or main thread
+            thread::yield_now();
+
+            unsafe {
+                ReleaseSRWLockShared(lock_ptr.0);
+            }
+            println!("reader {i} unlocked");
+        }));
+    }
+
+    // ensures each reader has acquired the lock
+    thread::yield_now();
+
+    unsafe {
+        WakeAllConditionVariable(condvar_ptr.0);
+    }
+
+    for handle in handles {
+        handle.join().unwrap();
+    }
+}
+
+// reacquiring a lock should wait until the lock is not exclusively locked
+fn shared_sleep_and_exclusive_lock() {
+    println!("shared_sleep_and_exclusive_lock");
+
+    let mut lock = null_mut();
+    let mut condvar = null_mut();
+
+    let lock_ptr = SendPtr(&mut lock);
+    let condvar_ptr = SendPtr(&mut condvar);
+
+    let mut waiters = Vec::with_capacity(5);
+    for i in 0..5 {
+        waiters.push(thread::spawn(move || {
+            unsafe {
+                AcquireSRWLockShared(lock_ptr.0);
+            }
+            println!("shared waiter {i} locked");
+
+            let r = unsafe {
+                SleepConditionVariableSRW(
+                    condvar_ptr.0,
+                    lock_ptr.0,
+                    INFINITE,
+                    CONDITION_VARIABLE_LOCKMODE_SHARED,
+                )
+            };
+            assert_ne!(r, 0);
+
+            println!("shared waiter {i} reacquired lock");
+
+            // unlocking is unnecessary because the lock is never used again
+        }));
+    }
+
+    // ensures each waiter is waiting by this point
+    thread::yield_now();
+
+    unsafe {
+        AcquireSRWLockExclusive(lock_ptr.0);
+    }
+    println!("main locked");
+
+    unsafe {
+        WakeAllConditionVariable(condvar_ptr.0);
+    }
+
+    // waiters are now waiting for the lock to be unlocked
+    thread::yield_now();
+
+    unsafe {
+        ReleaseSRWLockExclusive(lock_ptr.0);
+    }
+    println!("main unlocked");
+
+    for handle in waiters {
+        handle.join().unwrap();
+    }
+}
+
+// threads reacquiring locks should wait for all locks to be released first
+fn exclusive_sleep_and_shared_lock() {
+    println!("exclusive_sleep_and_shared_lock");
+
+    let mut lock = null_mut();
+    let mut condvar = null_mut();
+
+    let lock_ptr = SendPtr(&mut lock);
+    let condvar_ptr = SendPtr(&mut condvar);
+
+    let mut handles = Vec::with_capacity(10);
+    for i in 0..5 {
+        handles.push(thread::spawn(move || {
+            unsafe {
+                AcquireSRWLockExclusive(lock_ptr.0);
+            }
+
+            println!("exclusive waiter {i} locked");
+
+            let r = unsafe { SleepConditionVariableSRW(condvar_ptr.0, lock_ptr.0, INFINITE, 0) };
+            assert_ne!(r, 0);
+
+            println!("exclusive waiter {i} reacquired lock");
+
+            // switch to next waiter or main thread
+            thread::yield_now();
+
+            unsafe {
+                ReleaseSRWLockExclusive(lock_ptr.0);
+            }
+            println!("exclusive waiter {i} unlocked");
+        }));
+    }
+
+    for i in 0..5 {
+        handles.push(thread::spawn(move || {
+            unsafe {
+                AcquireSRWLockShared(lock_ptr.0);
+            }
+            println!("reader {i} locked");
+
+            // switch to next reader or main thread
+            thread::yield_now();
+
+            unsafe {
+                ReleaseSRWLockShared(lock_ptr.0);
+            }
+            println!("reader {i} unlocked");
+        }));
+    }
+
+    // ensures each reader has acquired the lock
+    thread::yield_now();
+
+    unsafe {
+        WakeAllConditionVariable(condvar_ptr.0);
+    }
+
+    for handle in handles {
+        handle.join().unwrap();
+    }
+}
+
+fn main() {
+    all_shared();
+    shared_sleep_and_exclusive_lock();
+    exclusive_sleep_and_shared_lock();
+}
diff --git a/src/tools/miri/tests/pass/concurrency/windows_condvar_shared.stdout b/src/tools/miri/tests/pass/concurrency/windows_condvar_shared.stdout
new file mode 100644
index 00000000000..918b54668f2
--- /dev/null
+++ b/src/tools/miri/tests/pass/concurrency/windows_condvar_shared.stdout
@@ -0,0 +1,60 @@
+all_shared
+exclusive waiter 0 locked
+exclusive waiter 1 locked
+exclusive waiter 2 locked
+exclusive waiter 3 locked
+exclusive waiter 4 locked
+reader 0 locked
+reader 1 locked
+reader 2 locked
+reader 3 locked
+reader 4 locked
+exclusive waiter 0 reacquired lock
+exclusive waiter 1 reacquired lock
+exclusive waiter 2 reacquired lock
+exclusive waiter 3 reacquired lock
+exclusive waiter 4 reacquired lock
+reader 0 unlocked
+reader 1 unlocked
+reader 2 unlocked
+reader 3 unlocked
+reader 4 unlocked
+shared_sleep_and_exclusive_lock
+shared waiter 0 locked
+shared waiter 1 locked
+shared waiter 2 locked
+shared waiter 3 locked
+shared waiter 4 locked
+main locked
+main unlocked
+shared waiter 0 reacquired lock
+shared waiter 1 reacquired lock
+shared waiter 2 reacquired lock
+shared waiter 3 reacquired lock
+shared waiter 4 reacquired lock
+exclusive_sleep_and_shared_lock
+exclusive waiter 0 locked
+exclusive waiter 1 locked
+exclusive waiter 2 locked
+exclusive waiter 3 locked
+exclusive waiter 4 locked
+reader 0 locked
+reader 1 locked
+reader 2 locked
+reader 3 locked
+reader 4 locked
+reader 0 unlocked
+reader 1 unlocked
+reader 2 unlocked
+reader 3 unlocked
+reader 4 unlocked
+exclusive waiter 0 reacquired lock
+exclusive waiter 0 unlocked
+exclusive waiter 1 reacquired lock
+exclusive waiter 1 unlocked
+exclusive waiter 2 reacquired lock
+exclusive waiter 2 unlocked
+exclusive waiter 3 reacquired lock
+exclusive waiter 3 unlocked
+exclusive waiter 4 reacquired lock
+exclusive waiter 4 unlocked
diff --git a/src/tools/miri/tests/pass/panic/concurrent-panic.rs b/src/tools/miri/tests/pass/panic/concurrent-panic.rs
index 342269c6acb..776bc2057f3 100644
--- a/src/tools/miri/tests/pass/panic/concurrent-panic.rs
+++ b/src/tools/miri/tests/pass/panic/concurrent-panic.rs
@@ -1,4 +1,3 @@
-//@ignore-target-windows: Condvars on Windows are not supported yet.
 // We are making scheduler assumptions here.
 //@compile-flags: -Zmiri-preemption-rate=0