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