process_sync/
mutex.rs

1use libc::{
2    pid_t, pthread_mutex_destroy, pthread_mutex_init, pthread_mutex_lock, pthread_mutex_t,
3    pthread_mutex_unlock, pthread_mutexattr_destroy, pthread_mutexattr_init,
4    pthread_mutexattr_setpshared, pthread_mutexattr_t, PTHREAD_MUTEX_INITIALIZER,
5    PTHREAD_PROCESS_SHARED,
6};
7
8use crate::{
9    shared_memory::SharedMemoryObject,
10    util::{check_libc_err, getpid},
11};
12
13/// Simple mutex that can be shared between processes.
14///
15/// This mutex is **NOT** recursive, so it will deadlock on relock.
16///
17/// Dropping mutex in creating process while mutex being locked or waited will cause undefined behaviour.
18/// It is recommended to drop this mutex in creating process only after no other process has access to it.
19///
20/// For more information see [`pthread_mutex_init`](https://man7.org/linux/man-pages/man3/pthread_mutex_destroy.3p.html), [`pthread_mutex_lock`](https://man7.org/linux/man-pages/man3/pthread_mutex_lock.3p.html) and [`SharedMemoryObject`].
21///
22/// # Example
23/// ```rust
24/// # use std::error::Error;
25/// # use std::thread::sleep;
26/// # use std::time::Duration;
27/// #
28/// # use libc::fork;
29/// #
30/// # use process_sync::private::check_libc_err;
31/// # use process_sync::SharedMutex;
32/// #
33/// # fn main() -> Result<(), Box<dyn Error>> {
34/// #
35/// let mut mutex = SharedMutex::new()?;
36///
37/// let pid = unsafe { fork() };
38/// assert!(pid >= 0);
39///
40/// if pid == 0 {
41///     println!("child lock()");
42///     mutex.lock()?;
43///     println!("child locked");
44///     sleep(Duration::from_millis(40));
45///     println!("child unlock()");
46///     mutex.unlock()?;
47/// } else {
48///     sleep(Duration::from_millis(20));
49///     println!("parent lock()");
50///     mutex.lock()?;
51///     println!("parent locked");
52///     sleep(Duration::from_millis(20));
53///     println!("parent unlock()");
54///     mutex.unlock()?;
55/// }
56/// #
57/// #     Ok(())
58/// # }
59/// ```
60///
61/// Output:
62/// ```txt
63/// child lock()
64/// child locked
65/// parent lock()
66/// child unlock()
67/// parent locked
68/// parent unlock()
69/// ```
70pub struct SharedMutex {
71    mutex: SharedMemoryObject<pthread_mutex_t>,
72    owner_pid: pid_t,
73}
74
75impl SharedMutex {
76    /// Creates new [`SharedMutex`]
77    ///
78    /// # Errors
79    /// If allocation or initialization fails returns error from [`last_os_error`].
80    ///
81    /// [`last_os_error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html#method.last_os_error.
82    pub fn new() -> std::io::Result<Self> {
83        let mut mutex = SharedMemoryObject::new(PTHREAD_MUTEX_INITIALIZER)?;
84        initialize_mutex(mutex.get_mut())?;
85
86        let owner_pid = getpid();
87        Ok(Self { mutex, owner_pid })
88    }
89
90    /// Locks mutex.
91    ///
92    /// This function will block until mutex is locked.
93    ///
94    /// # Errors
95    /// If any pthread call fails, returns error from [`last_os_error`]. For possible errors see [`pthread_mutex_lock`](https://man7.org/linux/man-pages/man3/pthread_mutex_lock.3p.html).
96    ///
97    /// [`last_os_error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html#method.last_os_error
98    pub fn lock(&mut self) -> std::io::Result<()> {
99        check_libc_err(unsafe { pthread_mutex_lock(self.mutex.get_mut()) })?;
100        Ok(())
101    }
102
103    /// Unlocks mutex.
104    ///
105    /// This function must be called from the same process that called [`lock`](#method.lock) previously.
106    ///
107    /// # Errors
108    /// If any pthread call fails, returns error from [`last_os_error`]. For possible errors see [`pthread_mutex_unlock`](https://man7.org/linux/man-pages/man3/pthread_mutex_lock.3p.html).
109    ///
110    /// [`last_os_error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html#method.last_os_error
111    pub fn unlock(&mut self) -> std::io::Result<()> {
112        check_libc_err(unsafe { pthread_mutex_unlock(self.mutex.get_mut()) })?;
113        Ok(())
114    }
115
116    pub(crate) fn get_mut(&mut self) -> *mut pthread_mutex_t {
117        self.mutex.get_mut()
118    }
119}
120
121// TODO: document drop behaviour
122impl Drop for SharedMutex {
123    fn drop(&mut self) {
124        if getpid() == self.owner_pid {
125            check_libc_err(unsafe { pthread_mutex_destroy(self.mutex.get_mut()) })
126                .expect("cannot destroy mutex");
127        }
128    }
129}
130
131fn initialize_mutex(mutex: &mut pthread_mutex_t) -> std::io::Result<()> {
132    let mut attr: pthread_mutexattr_t = unsafe { std::mem::zeroed() };
133    check_libc_err(unsafe { pthread_mutexattr_init(&mut attr) })?;
134
135    check_libc_err(unsafe { pthread_mutexattr_setpshared(&mut attr, PTHREAD_PROCESS_SHARED) })
136        .expect("cannot set PTHREAD_PROCESS_SHARED");
137
138    let ret = check_libc_err(unsafe { pthread_mutex_init(mutex, &mut attr) });
139
140    destroy_mutexattr(attr).expect("cannot destroy mutexattr");
141
142    ret.map(|_| ())
143}
144
145fn destroy_mutexattr(mut attr: pthread_mutexattr_t) -> std::io::Result<()> {
146    check_libc_err(unsafe { pthread_mutexattr_destroy(&mut attr) })?;
147    Ok(())
148}