Change:
This behavioral difference should factor into the stabilization of the methods on and how we'd like to word the documentation. For now though, this is a tracking issue as this difference in behavior was not originally intended. cc
How can this make the semantics of the API differ substantially? You have a borrow of the value protected by the Mutex, so even if the is reentrant I don't see how you could expose that through the API. Or I guess the issue is that you could get aliased s by ing a on Windows and unlocking both in the same thread?
Unfortunately the borrow is earned from the structure, not the , which allows you lock it twice. For example, if this code completed, it could exhibit memory unsafety (two references): On unix that code deadlocks, but on windows it executes just fine.
Wait, doesn't take a mutable reference to the ? Weird, isn't that guaranteed to deadlock on Unix? Anyway, since you could have aliased s in the same thread even if that were changed, I think Windows mutexes need an additional check for safety (Windows fast mutexes look like they have too many limitations to be a drop in replacement).
can't take . That'd be a guarantee that the caller has exclusive access to the mutex, which defeats the entire point.
Oh right, it has to be shared in the first place. Heh.
What are the limitations of fast mutexes that prevent us from using them?
and I discussed this and he recommends just adding a flag and checking it manually on Windows.
I don't think there are any limitations (we're currently using them), this is just a surprising consequence of using them. yeah I suspect that will have specific code on windows for a separate which is inspected once the lock is acquired (using ) and if the same thread acquires the mutex twice it just panics.
According to the current code we use Critical Sections for Mutexes on Windows. Fast/Guarded mutexes are a separate thing entirely and will deadlock if you attempt to lock them twice on the same thread. The MSVC C++ implementation for checks if the locked thread is the current thread before even trying to lock it and throws an exception if it is.
Whoa! I had no idea that "fast mutexes" was a thing. I thought there were only and . It looks like the "fast mutexes" or "guarded mutexes" are only for drivers, and maybe not for userland code? Or at least I couldn't figure out how to use them. Do you have pointers to the source for MSVC's implementation, or how to use them?
Yeah, the bits that made it seem like they were only for drivers was why I said we probably couldn't use them. Apparently critical sections are the fastest userland ones you should use. On a related note, it might be worth clarifying in the documentation that they aren't intended to be used between processes (maybe it's already there, but I don't recall seeing it).
According to some debugging, on Windows 8.1 x64, MSVC uses for instead of . I'm going to do some benchmarking to determine their performance difference. As an bonus, you can sleep a on an in Windows which Rust could provide as a platform specific extension trait.
And this is pretty conclusive. I did a test with two threads constantly locking and unlocking the same mutex. When I used it took about 260ns per lock+unlock. When I used it took about 60ns per lock+unlock.
On my beefy Win7 x64 gaming rig, I get a rock-solid 26ns for RW, and a fluctuating 70-80ns for Critical. Faster and more reliable.
Oh wow, and they can't be acquired recursively either, so we wouldn't even need to do anything different from the other platforms. Good find!
- Yep just tried acquiring an twice on the same thread and it has deadlocked.
Hm, they look like they have other problems though: Defaulting to an unfair lock doesn't seem like a good idea (unless your only goal is to win benchmarks). But if MSVC++ is doing it...
We already use for in Rust anyway.
what's the simple solution you have in mind? (Storing an extra flag internally?)
Some quick tests show that is indeed fairer than . CriticalSection: SRWLock: Each row is 100ms apart. CPU has 4 cores/threads.
If you write a semaphore on top of it and hard-wire the count at 1, it will deadlock. There is probably a better way to do it. I think the older mutex type is already non-recursive but I think it's missing the syscall free fast path.
As of Windows Server 2003 SP1, doesn't use FIFO, which means it doesn't actually guarantee fairness. If we're going to provide fair mutexes then we need to provide a solid fairness guarantee otherwise we might as well just use whatever is fastest.
After some clarification on IRC I have realized that fairness means low priority threads won't steal mutex lock time from higher priority threads.
Apparently [] is non-recursive on Linux too, and easy to deadlock:

Discussion:
#[inline] pub unsafe fn wait(&self, mutex: &Mutex) { <del> let r = ffi::SleepConditionVariableCS(self.inner.get(), mutex::raw(mutex), libc::INFINITE); </del> <ins> let r = ffi::SleepConditionVariableSRW(self.inner.get(), mutex::raw(mutex), libc::INFINITE, 0); </ins> debug_assert!(r != 0); } pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool { <del> let r = ffi::SleepConditionVariableCS(self.inner.get(), mutex::raw(mutex), dur.num_milliseconds() as DWORD); </del> <ins> let r = ffi::SleepConditionVariableSRW(self.inner.get(), mutex::raw(mutex), dur.num_milliseconds() as DWORD, 0); </ins> if r == 0 { const ERROR_TIMEOUT: DWORD = 0x5B4; debug_assert_eq!(os::errno() as uint, ERROR_TIMEOUT as uint);

// option. This file may not be copied, modified, or distributed // except according to those terms. <del> use prelude::v1::*; use sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; use alloc::{self, heap}; use libc::DWORD; </del> <ins> use marker::Sync; use cell::UnsafeCell; </ins> use sys::sync as ffi; <del> const SPIN_COUNT: DWORD = 4000; </del> <ins> pub struct Mutex { inner: UnsafeCell<ffi::SRWLOCK> } </ins> <del> pub struct Mutex { inner: AtomicUsize } pub const MUTEX_INIT: Mutex = Mutex { inner: ATOMIC_USIZE_INIT }; </del> <ins> pub const MUTEX_INIT: Mutex = Mutex { inner: UnsafeCell { value: ffi::SRWLOCK_INIT } }; </ins> unsafe impl Sync for Mutex {} #[inline] <del> pub unsafe fn raw(m: &Mutex) -> ffi::LPCRITICAL_SECTION { m.get() </del> <ins> pub unsafe fn raw(m: &Mutex) -> ffi::PSRWLOCK { m.inner.get() </ins> } <ins> // So you might be asking why we're using SRWLock instead of CriticalSection? // // 1. SRWLock is several times faster than CriticalSection according to benchmarks performed on both // Windows 8 and Windows 7. // // 2. CriticalSection allows recursive locking while SRWLock deadlocks. The Unix implementation // deadlocks so consistency is preferred. See #19962 for more details. // // 3. While CriticalSection is fair and SRWLock is not, the current Rust policy is there there are // no guarantees of fairness. </ins> impl Mutex { #[inline] <del> pub unsafe fn new() -> Mutex { Mutex { inner: AtomicUsize::new(init_lock() as uint) } } </del> <ins> pub unsafe fn new() -> Mutex { MUTEX_INIT } </ins> #[inline] pub unsafe fn lock(&self) { <del> ffi::EnterCriticalSection(self.get()) </del> <ins> ffi::AcquireSRWLockExclusive(self.inner.get()) </ins> } #[inline] pub unsafe fn try_lock(&self) -> bool { <del> ffi::TryEnterCriticalSection(self.get()) != 0 </del> <ins> ffi::TryAcquireSRWLockExclusive(self.inner.get()) != 0 </ins> } #[inline] pub unsafe fn unlock(&self) { <del> ffi::LeaveCriticalSection(self.get()) </del> <ins> ffi::ReleaseSRWLockExclusive(self.inner.get()) </ins> } <ins> #[inline] </ins> pub unsafe fn destroy(&self) { <del> let lock = self.inner.swap(0, Ordering::SeqCst); if lock != 0 { free_lock(lock as ffi::LPCRITICAL_SECTION) } } unsafe fn get(&self) -> ffi::LPCRITICAL_SECTION { match self.inner.load(Ordering::SeqCst) { 0 => {} n => return n as ffi::LPCRITICAL_SECTION } let lock = init_lock(); match self.inner.compare_and_swap(0, lock as uint, Ordering::SeqCst) { 0 => return lock as ffi::LPCRITICAL_SECTION, _ => {} } free_lock(lock); return self.inner.load(Ordering::SeqCst) as ffi::LPCRITICAL_SECTION; </del> <ins> // ... </ins> } } <del> unsafe fn init_lock() -> ffi::LPCRITICAL_SECTION { let block = heap::allocate(ffi::CRITICAL_SECTION_SIZE, 8) as ffi::LPCRITICAL_SECTION; if block.is_null() { alloc::oom() } ffi::InitializeCriticalSectionAndSpinCount(block, SPIN_COUNT); return block; } unsafe fn free_lock(h: ffi::LPCRITICAL_SECTION) { ffi::DeleteCriticalSection(h); heap::deallocate(h as *mut _, ffi::CRITICAL_SECTION_SIZE, 8); } </del>

// option. This file may not be copied, modified, or distributed // except according to those terms. <del> use libc::{BOOL, DWORD, c_void, LPVOID}; </del> <ins> use libc::{BOOL, DWORD, c_void, LPVOID, c_ulong}; </ins> use libc::types::os::arch::extra::BOOLEAN; <del> pub type LPCRITICAL_SECTION = *mut c_void; pub type LPCONDITION_VARIABLE = *mut CONDITION_VARIABLE; pub type LPSRWLOCK = *mut SRWLOCK; #[cfg(target_arch = "x86")] pub const CRITICAL_SECTION_SIZE: uint = 24; #[cfg(target_arch = "x86_64")] pub const CRITICAL_SECTION_SIZE: uint = 40; </del> <ins> pub type PCONDITION_VARIABLE = *mut CONDITION_VARIABLE; pub type PSRWLOCK = *mut SRWLOCK; pub type ULONG = c_ulong; </ins> #[repr(C)] pub struct CONDITION_VARIABLE { pub ptr: LPVOID }

pub const SRWLOCK_INIT: SRWLOCK = SRWLOCK { ptr: 0 as *mut _ }; extern "system" { <del> // critical sections pub fn InitializeCriticalSectionAndSpinCount( lpCriticalSection: LPCRITICAL_SECTION, dwSpinCount: DWORD) -> BOOL; pub fn DeleteCriticalSection(lpCriticalSection: LPCRITICAL_SECTION); pub fn EnterCriticalSection(lpCriticalSection: LPCRITICAL_SECTION); pub fn LeaveCriticalSection(lpCriticalSection: LPCRITICAL_SECTION); pub fn TryEnterCriticalSection(lpCriticalSection: LPCRITICAL_SECTION) -> BOOL; </del> // condition variables <del> pub fn SleepConditionVariableCS(ConditionVariable: LPCONDITION_VARIABLE, CriticalSection: LPCRITICAL_SECTION, dwMilliseconds: DWORD) -> BOOL; pub fn WakeConditionVariable(ConditionVariable: LPCONDITION_VARIABLE); pub fn WakeAllConditionVariable(ConditionVariable: LPCONDITION_VARIABLE); </del> <ins> pub fn SleepConditionVariableSRW(ConditionVariable: PCONDITION_VARIABLE, SRWLock: PSRWLOCK, dwMilliseconds: DWORD, Flags: ULONG) -> BOOL; pub fn WakeConditionVariable(ConditionVariable: PCONDITION_VARIABLE); pub fn WakeAllConditionVariable(ConditionVariable: PCONDITION_VARIABLE); </ins> // slim rwlocks <del> pub fn AcquireSRWLockExclusive(SRWLock: LPSRWLOCK); pub fn AcquireSRWLockShared(SRWLock: LPSRWLOCK); pub fn ReleaseSRWLockExclusive(SRWLock: LPSRWLOCK); pub fn ReleaseSRWLockShared(SRWLock: LPSRWLOCK); pub fn TryAcquireSRWLockExclusive(SRWLock: LPSRWLOCK) -> BOOLEAN; pub fn TryAcquireSRWLockShared(SRWLock: LPSRWLOCK) -> BOOLEAN; </del> <ins> pub fn AcquireSRWLockExclusive(SRWLock: PSRWLOCK); pub fn AcquireSRWLockShared(SRWLock: PSRWLOCK); pub fn ReleaseSRWLockExclusive(SRWLock: PSRWLOCK); pub fn ReleaseSRWLockShared(SRWLock: PSRWLOCK); pub fn TryAcquireSRWLockExclusive(SRWLock: PSRWLOCK) -> BOOLEAN; pub fn TryAcquireSRWLockShared(SRWLock: PSRWLOCK) -> BOOLEAN; </ins> }