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}