]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/mutex.rs
89c55eb859d0944daec0d28263bc2d72d48f4702
[rust.git] / library / std / src / sys / unix / mutex.rs
1 use crate::cell::UnsafeCell;
2 use crate::mem::MaybeUninit;
3 use crate::sys::cvt_nz;
4
5 pub struct Mutex {
6     inner: UnsafeCell<libc::pthread_mutex_t>,
7 }
8
9 pub type MovableMutex = Box<Mutex>;
10
11 #[inline]
12 pub unsafe fn raw(m: &Mutex) -> *mut libc::pthread_mutex_t {
13     m.inner.get()
14 }
15
16 unsafe impl Send for Mutex {}
17 unsafe impl Sync for Mutex {}
18
19 #[allow(dead_code)] // sys isn't exported yet
20 impl Mutex {
21     pub const fn new() -> Mutex {
22         // Might be moved to a different address, so it is better to avoid
23         // initialization of potentially opaque OS data before it landed.
24         // Be very careful using this newly constructed `Mutex`, reentrant
25         // locking is undefined behavior until `init` is called!
26         Mutex { inner: UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER) }
27     }
28     #[inline]
29     pub unsafe fn init(&mut self) {
30         // Issue #33770
31         //
32         // A pthread mutex initialized with PTHREAD_MUTEX_INITIALIZER will have
33         // a type of PTHREAD_MUTEX_DEFAULT, which has undefined behavior if you
34         // try to re-lock it from the same thread when you already hold a lock
35         // (https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_init.html).
36         // This is the case even if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_NORMAL
37         // (https://github.com/rust-lang/rust/issues/33770#issuecomment-220847521) -- in that
38         // case, `pthread_mutexattr_settype(PTHREAD_MUTEX_DEFAULT)` will of course be the same
39         // as setting it to `PTHREAD_MUTEX_NORMAL`, but not setting any mode will result in
40         // a Mutex where re-locking is UB.
41         //
42         // In practice, glibc takes advantage of this undefined behavior to
43         // implement hardware lock elision, which uses hardware transactional
44         // memory to avoid acquiring the lock. While a transaction is in
45         // progress, the lock appears to be unlocked. This isn't a problem for
46         // other threads since the transactional memory will abort if a conflict
47         // is detected, however no abort is generated when re-locking from the
48         // same thread.
49         //
50         // Since locking the same mutex twice will result in two aliasing &mut
51         // references, we instead create the mutex with type
52         // PTHREAD_MUTEX_NORMAL which is guaranteed to deadlock if we try to
53         // re-lock it from the same thread, thus avoiding undefined behavior.
54         let mut attr = MaybeUninit::<libc::pthread_mutexattr_t>::uninit();
55         cvt_nz(libc::pthread_mutexattr_init(attr.as_mut_ptr())).unwrap();
56         let attr = PthreadMutexAttr(&mut attr);
57         cvt_nz(libc::pthread_mutexattr_settype(attr.0.as_mut_ptr(), libc::PTHREAD_MUTEX_NORMAL))
58             .unwrap();
59         cvt_nz(libc::pthread_mutex_init(self.inner.get(), attr.0.as_ptr())).unwrap();
60     }
61     #[inline]
62     pub unsafe fn lock(&self) {
63         let r = libc::pthread_mutex_lock(self.inner.get());
64         debug_assert_eq!(r, 0);
65     }
66     #[inline]
67     pub unsafe fn unlock(&self) {
68         let r = libc::pthread_mutex_unlock(self.inner.get());
69         debug_assert_eq!(r, 0);
70     }
71     #[inline]
72     pub unsafe fn try_lock(&self) -> bool {
73         libc::pthread_mutex_trylock(self.inner.get()) == 0
74     }
75     #[inline]
76     #[cfg(not(target_os = "dragonfly"))]
77     pub unsafe fn destroy(&self) {
78         let r = libc::pthread_mutex_destroy(self.inner.get());
79         debug_assert_eq!(r, 0);
80     }
81     #[inline]
82     #[cfg(target_os = "dragonfly")]
83     pub unsafe fn destroy(&self) {
84         let r = libc::pthread_mutex_destroy(self.inner.get());
85         // On DragonFly pthread_mutex_destroy() returns EINVAL if called on a
86         // mutex that was just initialized with libc::PTHREAD_MUTEX_INITIALIZER.
87         // Once it is used (locked/unlocked) or pthread_mutex_init() is called,
88         // this behaviour no longer occurs.
89         debug_assert!(r == 0 || r == libc::EINVAL);
90     }
91 }
92
93 pub struct ReentrantMutex {
94     inner: UnsafeCell<libc::pthread_mutex_t>,
95 }
96
97 unsafe impl Send for ReentrantMutex {}
98 unsafe impl Sync for ReentrantMutex {}
99
100 impl ReentrantMutex {
101     pub const unsafe fn uninitialized() -> ReentrantMutex {
102         ReentrantMutex { inner: UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER) }
103     }
104
105     pub unsafe fn init(&self) {
106         let mut attr = MaybeUninit::<libc::pthread_mutexattr_t>::uninit();
107         cvt_nz(libc::pthread_mutexattr_init(attr.as_mut_ptr())).unwrap();
108         let attr = PthreadMutexAttr(&mut attr);
109         cvt_nz(libc::pthread_mutexattr_settype(attr.0.as_mut_ptr(), libc::PTHREAD_MUTEX_RECURSIVE))
110             .unwrap();
111         cvt_nz(libc::pthread_mutex_init(self.inner.get(), attr.0.as_ptr())).unwrap();
112     }
113
114     pub unsafe fn lock(&self) {
115         let result = libc::pthread_mutex_lock(self.inner.get());
116         debug_assert_eq!(result, 0);
117     }
118
119     #[inline]
120     pub unsafe fn try_lock(&self) -> bool {
121         libc::pthread_mutex_trylock(self.inner.get()) == 0
122     }
123
124     pub unsafe fn unlock(&self) {
125         let result = libc::pthread_mutex_unlock(self.inner.get());
126         debug_assert_eq!(result, 0);
127     }
128
129     pub unsafe fn destroy(&self) {
130         let result = libc::pthread_mutex_destroy(self.inner.get());
131         debug_assert_eq!(result, 0);
132     }
133 }
134
135 struct PthreadMutexAttr<'a>(&'a mut MaybeUninit<libc::pthread_mutexattr_t>);
136
137 impl Drop for PthreadMutexAttr<'_> {
138     fn drop(&mut self) {
139         unsafe {
140             let result = libc::pthread_mutexattr_destroy(self.0.as_mut_ptr());
141             debug_assert_eq!(result, 0);
142         }
143     }
144 }