iceoryx2_bb_posix/
read_write_mutex.rs

1// Copyright (c) 2023 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13//! A POSIX inter-process capable [`ReadWriteMutex`] where either multiple readers can acquire
14//! multiple read-locks or one writer can acquire a write-lock.
15//! It is built by the [`ReadWriteMutexBuilder`].
16//!
17//! # Example
18//!
19//! ```no_run
20//! use iceoryx2_bb_posix::read_write_mutex::*;
21//! use std::thread;
22//! use core::time::Duration;
23//! use iceoryx2_bb_posix::clock::ClockType;
24//!
25//! let rw_handle = ReadWriteMutexHandle::new();
26//! let rw_mutex = ReadWriteMutexBuilder::new()
27//!                         .is_interprocess_capable(true)
28//!                         .create(123, &rw_handle)
29//!                         .expect("failed to create rw mutex");
30//!
31//! thread::scope(|s| {
32//!     s.spawn(|| {
33//!         let guard = rw_mutex.read_blocking_lock()
34//!                             .expect("failed to read_lock");
35//!         println!("The mutex value is: {}", *guard);
36//!     });
37//!
38//!     s.spawn(|| {
39//!         let mut guard = rw_mutex.write_blocking_lock()
40//!                                 .expect("failed to write_lock");
41//!         println!("The old value is: {}", *guard);
42//!         *guard = 456;
43//!         println!("The new value is: {}", *guard);
44//!     });
45//! });
46//! ```
47pub use crate::ipc_capable::{Handle, IpcCapable};
48
49use crate::handle_errno;
50use crate::ipc_capable::internal::{Capability, HandleStorage, IpcConstructible};
51use iceoryx2_bb_elementary::{enum_gen, scope_guard::ScopeGuardBuilder};
52use iceoryx2_bb_log::{fail, fatal_panic, warn};
53use iceoryx2_pal_posix::posix::errno::Errno;
54use iceoryx2_pal_posix::posix::MemZeroedStruct;
55use iceoryx2_pal_posix::*;
56
57use core::marker::PhantomData;
58use core::{
59    cell::UnsafeCell,
60    fmt::Debug,
61    ops::{Deref, DerefMut},
62};
63
64#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
65pub enum ReadWriteMutexCreationError {
66    InsufficientMemory,
67    InsufficientResources,
68    InsufficientPermissions,
69    NoInterProcessSupport,
70    NoMutexKindSupport,
71    UnknownError(i32),
72}
73
74#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
75pub enum ReadWriteMutexReadLockError {
76    MaximumAmountOfReadLocksAcquired,
77    DeadlockConditionDetected,
78    UnknownError(i32),
79}
80
81#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
82pub enum ReadWriteMutexUnlockError {
83    OwnedByDifferentEntity,
84    UnknownError(i32),
85}
86
87#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
88pub enum ReadWriteMutexWriteLockError {
89    DeadlockConditionDetected,
90    UnknownError(i32),
91}
92
93#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
94pub enum ReadWriteMutexOpenIpcHandleError {
95    IsNotInterProcessCapable,
96    Uninitialized,
97}
98
99enum_gen! {
100    /// The ReadWriteMutexError enum is a generalization when one doesn't require the fine-grained error
101    /// handling enums. One can forward ReadWriteMutexError as more generic return value when a method
102    /// returns a ReadWriteMutex***Error.
103    /// On a higher level it is again convertable to [`crate::Error`].
104    ReadWriteMutexError
105  generalization:
106    FailedToLock <= ReadWriteMutexWriteLockError; ReadWriteMutexReadLockError,
107    FailedToCreate <= ReadWriteMutexCreationError
108}
109
110/// The builder for the [`ReadWriteMutex`].
111#[derive(Debug)]
112pub struct ReadWriteMutexBuilder {
113    is_interprocess_capable: bool,
114}
115
116impl Default for ReadWriteMutexBuilder {
117    fn default() -> Self {
118        ReadWriteMutexBuilder {
119            is_interprocess_capable: true,
120        }
121    }
122}
123
124impl ReadWriteMutexBuilder {
125    pub fn new() -> Self {
126        Self::default()
127    }
128
129    /// Defines if the [`ReadWriteMutex`] is inter-process capable or not.
130    pub fn is_interprocess_capable(mut self, value: bool) -> Self {
131        self.is_interprocess_capable = value;
132        self
133    }
134
135    fn initialize_rw_mutex(
136        &self,
137        mtx: *mut posix::pthread_rwlock_t,
138    ) -> Result<Capability, ReadWriteMutexCreationError> {
139        let msg = "Failed to create mutex";
140        let origin = format!("{self:?}");
141
142        let mut attributes = ScopeGuardBuilder::new(posix::pthread_rwlockattr_t::new_zeroed()).on_init(|attr| {
143            handle_errno!(ReadWriteMutexCreationError, from self,
144                errno_source unsafe { posix::pthread_rwlockattr_init( attr).into() },
145                success Errno::ESUCCES => (),
146                Errno::ENOMEM => (InsufficientMemory, "{} due to insufficient memory while creating rwlock attributes.", msg),
147                v => (UnknownError(v as i32), "{} since an unknown error occurred while creating rwlock attributes.", msg)
148            );
149        }).on_drop(|attr| {
150            match unsafe {posix::pthread_rwlockattr_destroy(attr) } {
151                0 => (),
152                v => {
153                    fatal_panic!(from origin, "This should never happen! Failed to release rwlock attributes ({}).", v);
154                }
155            }
156        }).create()?;
157
158        match unsafe { posix::pthread_rwlockattr_setpshared(attributes.get_mut(), 0) } {
159            0 => (),
160            v => {
161                fail!(from origin, with ReadWriteMutexCreationError::NoInterProcessSupport,
162                        "{} due to an unknown error while setting interprocess capabilities ({}).", msg,v );
163            }
164        }
165
166        match unsafe { posix::pthread_rwlock_init(mtx, attributes.get()).into() } {
167            Errno::ESUCCES => (),
168            Errno::EAGAIN => {
169                fail!(from origin, with ReadWriteMutexCreationError::InsufficientResources, "{} due to insufficient resources.", msg);
170            }
171            Errno::ENOMEM => {
172                fail!(from origin, with ReadWriteMutexCreationError::InsufficientResources, "{} due to insufficient memory.", msg);
173            }
174            Errno::EPERM => {
175                fail!(from origin, with ReadWriteMutexCreationError::InsufficientPermissions, "{} due to insufficient permissions.", msg);
176            }
177            v => {
178                fail!(from origin, with ReadWriteMutexCreationError::UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v);
179            }
180        };
181
182        match self.is_interprocess_capable {
183            true => Ok(Capability::InterProcess),
184            false => Ok(Capability::ProcessLocal),
185        }
186    }
187
188    /// Creates a new [`ReadWriteMutex`]
189    pub fn create<T: Debug>(
190        self,
191        t: T,
192        handle: &ReadWriteMutexHandle<T>,
193    ) -> Result<ReadWriteMutex<'_, '_, T>, ReadWriteMutexCreationError> {
194        unsafe {
195            handle
196                .handle
197                .initialize(|mtx| self.initialize_rw_mutex(mtx))?
198        };
199
200        unsafe { *handle.value.get() = Some(t) };
201
202        Ok(ReadWriteMutex::new(handle))
203    }
204}
205
206/// A guard which provides read access to the underlying value of a [`ReadWriteMutex`].
207///
208/// Is returned by [`ReadWriteMutex::read_blocking_lock()`] and [`ReadWriteMutex::read_try_lock()`].
209#[derive(Debug)]
210pub struct MutexReadGuard<'handle, T: Debug> {
211    handle: &'handle ReadWriteMutexHandle<T>,
212}
213
214unsafe impl<T: Send + Debug> Send for MutexReadGuard<'_, T> {}
215unsafe impl<T: Send + Sync + Debug> Sync for MutexReadGuard<'_, T> {}
216
217impl<T: Debug> Deref for MutexReadGuard<'_, T> {
218    type Target = T;
219
220    fn deref(&self) -> &Self::Target {
221        unsafe { (*self.handle.value.get()).as_ref().unwrap() }
222    }
223}
224
225impl<T: Debug> Drop for MutexReadGuard<'_, T> {
226    fn drop(&mut self) {
227        if ReadWriteMutex::release(self.handle).is_err() {
228            fatal_panic!(from self, "This should never happen! Failed to release read lock.");
229        }
230    }
231}
232
233/// A guard which provides read and write access to the underlying value of a [`ReadWriteMutex`].
234///
235/// Is returned by [`ReadWriteMutex::write_blocking_lock()`] and [`ReadWriteMutex::write_try_lock()`].
236#[derive(Debug)]
237pub struct MutexWriteGuard<'handle, T: Debug> {
238    handle: &'handle ReadWriteMutexHandle<T>,
239}
240
241unsafe impl<T: Send + Debug> Send for MutexWriteGuard<'_, T> {}
242unsafe impl<T: Send + Sync + Debug> Sync for MutexWriteGuard<'_, T> {}
243
244impl<T: Debug> Deref for MutexWriteGuard<'_, T> {
245    type Target = T;
246
247    fn deref(&self) -> &Self::Target {
248        unsafe { (*self.handle.value.get()).as_ref().unwrap() }
249    }
250}
251
252impl<T: Debug> DerefMut for MutexWriteGuard<'_, T> {
253    fn deref_mut(&mut self) -> &mut Self::Target {
254        unsafe { (*self.handle.value.get()).as_mut().unwrap() }
255    }
256}
257
258impl<T: Debug> Drop for MutexWriteGuard<'_, T> {
259    fn drop(&mut self) {
260        if ReadWriteMutex::release(self.handle).is_err() {
261            fatal_panic!(from self, "This should never happen! Failed to release write lock.");
262        }
263    }
264}
265
266/// The underlying memory of a [`ReadWriteMutex`] is not allowed to be moved. This issue is solved
267/// by storing the underlying posix handle inside [`ReadWriteMutexHandle`]. When a [`ReadWriteMutex`]
268/// is initialized it stores a const reference to the [`ReadWriteMutexHandle`] and makes it by
269/// that inmovable.
270#[derive(Debug)]
271pub struct ReadWriteMutexHandle<T: Sized + Debug> {
272    handle: HandleStorage<posix::pthread_rwlock_t>,
273    value: UnsafeCell<Option<T>>,
274}
275
276unsafe impl<T: Sized + Debug> Send for ReadWriteMutexHandle<T> {}
277unsafe impl<T: Sized + Debug> Sync for ReadWriteMutexHandle<T> {}
278
279impl<T: Sized + Debug> Handle for ReadWriteMutexHandle<T> {
280    fn new() -> Self {
281        Self {
282            handle: HandleStorage::new(posix::pthread_rwlock_t::new_zeroed()),
283            value: UnsafeCell::new(None),
284        }
285    }
286
287    fn is_initialized(&self) -> bool {
288        self.handle.is_initialized()
289    }
290
291    fn is_inter_process_capable(&self) -> bool {
292        self.handle.is_inter_process_capable()
293    }
294}
295
296impl<T: Sized + Debug> Drop for ReadWriteMutexHandle<T> {
297    fn drop(&mut self) {
298        if self.handle.is_initialized() {
299            unsafe {
300                self.handle.cleanup(|mtx|{
301                    if posix::pthread_rwlock_destroy(mtx) != 0 {
302                        warn!(from self,
303                            "Unable to destroy read write mutex. Was it already destroyed by another instance in another process?");
304                    }
305                });
306            }
307        }
308    }
309}
310
311/// A POSIX read write mutex where either multiple readers can acquire multiple read-locks or one
312/// writer can acquire a write-lock.
313/// It is built by the [`ReadWriteMutexBuilder`].
314#[derive(Debug)]
315pub struct ReadWriteMutex<'this, 'handle: 'this, T: Sized + Debug> {
316    handle: &'handle ReadWriteMutexHandle<T>,
317    _lifetime: PhantomData<&'this ()>,
318}
319
320unsafe impl<T: Send + Debug> Send for ReadWriteMutex<'_, '_, T> {}
321unsafe impl<T: Sync + Debug> Sync for ReadWriteMutex<'_, '_, T> {}
322
323impl<'handle, T: Sized + Debug> IpcConstructible<'handle, ReadWriteMutexHandle<T>>
324    for ReadWriteMutex<'_, 'handle, T>
325{
326    fn new(handle: &'handle ReadWriteMutexHandle<T>) -> Self {
327        Self {
328            handle,
329            _lifetime: PhantomData,
330        }
331    }
332}
333
334impl<'handle, T: Sized + Debug> IpcCapable<'handle, ReadWriteMutexHandle<T>>
335    for ReadWriteMutex<'_, 'handle, T>
336{
337    fn is_interprocess_capable(&self) -> bool {
338        self.handle.is_inter_process_capable()
339    }
340}
341
342impl<'this, 'handle: 'this, T: Sized + Debug> ReadWriteMutex<'this, 'handle, T> {
343    /// Instantiates a [`ReadWriteMutex`] from an already initialized [`ReadWriteMutexHandle`].
344    /// Useful for inter-process usage where the [`ReadWriteMutexHandle`] was created by
345    /// [`ReadWriteMutexBuilder`] in another process.
346    ///
347    /// # Safety
348    ///
349    /// * `handle` must have been successfully initialized by the [`ReadWriteMutexBuilder`].
350    pub fn from_handle(
351        handle: &'handle ReadWriteMutexHandle<T>,
352    ) -> ReadWriteMutex<'this, 'handle, T> {
353        Self::new(handle)
354    }
355
356    fn new(handle: &'handle ReadWriteMutexHandle<T>) -> Self {
357        Self {
358            handle,
359            _lifetime: PhantomData,
360        }
361    }
362
363    pub fn read_blocking_lock(
364        &'this self,
365    ) -> Result<MutexReadGuard<'handle, T>, ReadWriteMutexReadLockError> {
366        let msg = "Failed to acquire read-lock";
367        handle_errno!(ReadWriteMutexReadLockError, from self,
368            errno_source unsafe { posix::pthread_rwlock_rdlock(self.handle.handle.get()).into() },
369            success Errno::ESUCCES => MutexReadGuard { handle: self.handle },
370            Errno::EAGAIN => (MaximumAmountOfReadLocksAcquired, "{} since the maximum amount of read-locks is already acquired.", msg),
371            Errno::EDEADLK => (DeadlockConditionDetected, "{} since a deadlock condition was detected.", msg),
372            v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
373        );
374    }
375
376    /// Tries to acquire a read-lock. If a write-locks was already acquired it returns [`None`]
377    /// otherwise a [`MutexReadGuard`].
378    pub fn read_try_lock(
379        &'this self,
380    ) -> Result<Option<MutexReadGuard<'handle, T>>, ReadWriteMutexReadLockError> {
381        let msg = "Failed to try to acquire read-lock";
382        handle_errno!(ReadWriteMutexReadLockError, from self,
383            errno_source unsafe { posix::pthread_rwlock_tryrdlock(self.handle.handle.get()).into() },
384            success Errno::ESUCCES => Some(MutexReadGuard { handle: self.handle });
385            success Errno::EBUSY => None;
386            success Errno::EDEADLK => None,
387            Errno::EAGAIN => (MaximumAmountOfReadLocksAcquired, "{} since the maximum amount of read-locks is already acquired.", msg),
388            v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
389        );
390    }
391
392    /// Blocks until a write-lock could be acquired and returns a [`MutexWriteGuard`] to provide
393    /// read-write access to the underlying value.
394    pub fn write_blocking_lock(
395        &'this self,
396    ) -> Result<MutexWriteGuard<'handle, T>, ReadWriteMutexWriteLockError> {
397        let msg = "Failed to acquire write-lock";
398        handle_errno!(ReadWriteMutexWriteLockError, from self,
399            errno_source unsafe { posix::pthread_rwlock_wrlock(self.handle.handle.get()).into() },
400            success Errno::ESUCCES => MutexWriteGuard { handle: self.handle },
401            Errno::EDEADLK => (DeadlockConditionDetected, "{} since a deadlock condition was detected.", msg),
402            v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
403        );
404    }
405
406    /// Tries to acquire a read-lock. If a read-locks was already acquired it returns [`None`]
407    /// otherwise a [`MutexWriteGuard`].
408    pub fn write_try_lock(
409        &'this self,
410    ) -> Result<Option<MutexWriteGuard<'handle, T>>, ReadWriteMutexWriteLockError> {
411        let msg = "Failed to try to acquire write-lock";
412        handle_errno!(ReadWriteMutexWriteLockError, from self,
413            errno_source unsafe { posix::pthread_rwlock_trywrlock(self.handle.handle.get()).into() },
414            success Errno::ESUCCES => Some(MutexWriteGuard { handle: self.handle });
415            success Errno::EBUSY => None;
416            success Errno::EDEADLK => None,
417            v => (UnknownError(v as i32), "{} since an unknown error occurred ({}).", msg, v)
418        );
419    }
420
421    fn release(handle: &ReadWriteMutexHandle<T>) -> Result<(), ReadWriteMutexUnlockError> {
422        let msg = "Unable to release lock";
423        match unsafe { posix::pthread_rwlock_unlock(handle.handle.get()).into() } {
424            Errno::ESUCCES => Ok(()),
425            Errno::EPERM => {
426                fail!(from handle, with ReadWriteMutexUnlockError::OwnedByDifferentEntity,
427                    "{} since it is not owned by the current thread.", msg);
428            }
429            v => {
430                fail!(from handle, with ReadWriteMutexUnlockError::UnknownError(v as i32),
431                    "{} since an unknown error occurred ({}).", msg, v);
432            }
433        }
434    }
435}