]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/locks/fuchsia_mutex.rs
7372406b32fac683dba4a8a68a97db4ad2ec4e8c
[rust.git] / library / std / src / sys / unix / locks / fuchsia_mutex.rs
1 //! A priority inheriting mutex for Fuchsia.
2 //!
3 //! This is a port of the [mutex in Fuchsia's libsync]. Contrary to the original,
4 //! it does not abort the process when reentrant locking is detected, but deadlocks.
5 //!
6 //! Priority inheritance is achieved by storing the owning thread's handle in an
7 //! atomic variable. Fuchsia's futex operations support setting an owner thread
8 //! for a futex, which can boost that thread's priority while the futex is waited
9 //! upon.
10 //!
11 //! libsync is licenced under the following BSD-style licence:
12 //!
13 //! Copyright 2016 The Fuchsia Authors.
14 //!
15 //! Redistribution and use in source and binary forms, with or without
16 //! modification, are permitted provided that the following conditions are
17 //! met:
18 //!
19 //!    * Redistributions of source code must retain the above copyright
20 //!      notice, this list of conditions and the following disclaimer.
21 //!    * Redistributions in binary form must reproduce the above
22 //!      copyright notice, this list of conditions and the following
23 //!      disclaimer in the documentation and/or other materials provided
24 //!      with the distribution.
25 //!
26 //! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 //! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 //! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 //! A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 //! OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 //! SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 //! LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 //! DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 //! THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 //! (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 //! OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 //!
38 //! [mutex in Fuchsia's libsync]: https://cs.opensource.google/fuchsia/fuchsia/+/main:zircon/system/ulib/sync/mutex.c
39
40 use crate::sync::atomic::{
41     AtomicU32,
42     Ordering::{Acquire, Relaxed, Release},
43 };
44 use crate::sys::futex::zircon::{
45     zx_futex_wait, zx_futex_wake_single_owner, zx_handle_t, zx_thread_self, ZX_ERR_BAD_HANDLE,
46     ZX_ERR_BAD_STATE, ZX_ERR_INVALID_ARGS, ZX_ERR_TIMED_OUT, ZX_ERR_WRONG_TYPE, ZX_OK,
47     ZX_TIME_INFINITE, ZX_TIME_INFINITE,
48 };
49
50 // The lowest two bits of a `zx_handle_t` are always set, so the lowest bit is used to mark the
51 // mutex as contested by clearing it.
52 const CONTESTED_BIT: u32 = 1;
53 // This can never be a valid `zx_handle_t`.
54 const UNLOCKED: u32 = 0;
55
56 pub type MovableMutex = Mutex;
57
58 pub struct Mutex {
59     futex: AtomicU32,
60 }
61
62 #[inline]
63 fn to_state(owner: zx_handle_t) -> u32 {
64     owner
65 }
66
67 #[inline]
68 fn to_owner(state: u32) -> zx_handle_t {
69     state | CONTESTED_BIT
70 }
71
72 #[inline]
73 fn is_contested(state: u32) -> bool {
74     state & CONTESTED_BIT == 0
75 }
76
77 #[inline]
78 fn mark_contested(state: u32) -> u32 {
79     state & !CONTESTED_BIT
80 }
81
82 impl Mutex {
83     #[inline]
84     pub const fn new() -> Mutex {
85         Mutex { futex: AtomicU32::new(UNLOCKED) }
86     }
87
88     #[inline]
89     pub unsafe fn init(&mut self) {}
90
91     #[inline]
92     pub unsafe fn try_lock(&self) -> bool {
93         let thread_self = zx_thread_self();
94         self.futex.compare_exchange(UNLOCKED, to_state(thread_self), Acquire, Relaxed).is_ok()
95     }
96
97     #[inline]
98     pub unsafe fn lock(&self) {
99         let thread_self = zx_thread_self();
100         if let Err(state) =
101             self.futex.compare_exchange(UNLOCKED, to_state(thread_self), Acquire, Relaxed)
102         {
103             self.lock_contested(state, thread_self);
104         }
105     }
106
107     #[cold]
108     fn lock_contested(&self, mut state: u32, thread_self: zx_handle_t) {
109         let owned_state = mark_contested(to_state(thread_self));
110         loop {
111             // Mark the mutex as contested if it is not already.
112             let contested = mark_contested(state);
113             if is_contested(state)
114                 || self.futex.compare_exchange(state, contested, Relaxed, Relaxed).is_ok()
115             {
116                 // The mutex has been marked as contested, wait for the state to change.
117                 unsafe {
118                     match zx_futex_wait(
119                         &self.futex,
120                         AtomicU32::new(contested),
121                         to_owner(state),
122                         ZX_TIME_INFINITE,
123                     ) {
124                         ZX_OK | ZX_ERR_BAD_STATE | ZX_ERR_TIMED_OUT => (),
125                         // Note that if a thread handle is reused after its associated thread
126                         // exits without unlocking the mutex, an arbitrary thread's priority
127                         // could be boosted by the wait, but there is currently no way to
128                         // prevent that.
129                         ZX_ERR_INVALID_ARGS | ZX_ERR_BAD_HANDLE | ZX_ERR_WRONG_TYPE => {
130                             panic!(
131                                 "either the current thread is trying to lock a mutex it has
132                                 already locked, or the previous uowner did not unlock the mutex
133                                 before exiting"
134                             )
135                         }
136                         error => panic!("unexpected error in zx_futex_wait: {error}"),
137                     }
138                 }
139             }
140
141             // The state has changed or a wakeup occured, try to lock the mutex.
142             match self.futex.compare_exchange(UNLOCKED, owned_state, Acquire, Relaxed) {
143                 Ok(_) => return,
144                 Err(updated) => state = updated,
145             }
146         }
147     }
148
149     #[inline]
150     pub unsafe fn unlock(&self) {
151         if is_contested(self.futex.swap(UNLOCKED, Release)) {
152             // The woken thread will mark the mutex as contested again,
153             // and return here, waking until there are no waiters left,
154             // in which case this is a noop.
155             self.wake();
156         }
157     }
158
159     #[cold]
160     fn wake(&self) {
161         unsafe {
162             zx_futex_wake_single_owner(&self.futex);
163         }
164     }
165 }