named_semaphore/
lib.rs

1use std::ffi::CString;
2use std::fmt::{Debug, Formatter};
3use std::io::{Error, Result};
4use std::os::raw::c_int;
5
6pub struct Semaphore {
7    name: CString,
8    sem: *mut libc::sem_t,
9}
10
11impl Debug for Semaphore {
12    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
13        write!(f, "Semaphore{{ name: \"{}\"}} ", self.name.to_string_lossy())?;
14        Ok(())
15    }
16}
17
18impl Semaphore {
19    #[must_use]
20    pub fn open(name: &str, capacity: usize) -> Result<Semaphore> {
21        Semaphore::open_with_oflag(name, capacity, libc::O_CREAT)
22    }
23
24    #[must_use]
25    pub fn create(name: &str, capacity: usize) -> Result<Semaphore> {
26        Semaphore::open_with_oflag(name, capacity, libc::O_CREAT | libc::O_EXCL)
27    }
28
29    fn open_with_oflag(name: &str, capacity: usize, oflag: c_int) -> Result<Semaphore> {
30        let (name, sem) = unsafe {
31            let name = CString::new(name)?;
32            let sem = libc::sem_open(name.as_ptr(), oflag, 0o644, capacity);
33            (name, sem)
34        };
35        if sem == libc::SEM_FAILED {
36            return Err(Error::last_os_error());
37        }
38        Ok(Semaphore { name, sem })
39    }
40
41    #[must_use]
42    pub fn value(&self) -> Result<usize> {
43        let sval = &mut 0;
44        capture_io_error(|| unsafe { libc::sem_getvalue(self.sem, sval) })?;
45        if *sval < 0 {
46            *sval = 0;
47        }
48        Ok(*sval as usize)
49    }
50
51    #[must_use]
52    pub fn acquire(&self) -> Result<()> {
53        capture_io_error(|| unsafe { libc::sem_wait(self.sem) })?;
54        Ok(())
55    }
56
57    #[must_use]
58    pub fn try_acquire(&self) -> Result<()> {
59        capture_io_error(|| unsafe { libc::sem_trywait(self.sem) })?;
60        Ok(())
61    }
62
63    #[must_use]
64    pub fn release(&self) -> Result<()> {
65        capture_io_error(|| unsafe { libc::sem_post(self.sem) })
66    }
67
68    #[must_use]
69    pub fn access(&self) -> Result<SemaphoreGuard> {
70        self.acquire()?;
71        Ok(unsafe { SemaphoreGuard::new(self) })
72    }
73
74    #[must_use]
75    pub fn try_access(&self) -> Result<SemaphoreGuard> {
76        self.try_acquire()?;
77        Ok(unsafe { SemaphoreGuard::new(self) })
78    }
79
80    #[must_use]
81    pub fn close(self) -> Result<()> {
82        capture_io_error(|| unsafe { libc::sem_close(self.sem) })
83    }
84
85    #[must_use]
86    pub fn unlink(&self) -> Result<()> {
87        capture_io_error(|| unsafe { libc::sem_unlink(self.name.as_ptr()) })
88    }
89}
90
91#[inline(always)]
92fn capture_io_error(f: impl FnOnce() -> c_int) -> Result<()> {
93    let result = f();
94    if result != 0 {
95        return Err(Error::last_os_error());
96    }
97    Ok(())
98}
99
100impl Drop for Semaphore {
101    fn drop(&mut self) {
102        let _ = capture_io_error(|| unsafe { libc::sem_close(self.sem) });
103    }
104}
105
106pub struct SemaphoreGuard<'a> {
107    sem: &'a Semaphore,
108}
109
110impl Debug for SemaphoreGuard<'_> {
111    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
112        write!(f, "SemaphoreGuard {{ name: \"{}\" }}", self.sem.name.to_string_lossy())?;
113        Ok(())
114    }
115}
116
117impl<'a> SemaphoreGuard<'a> {
118    unsafe fn new(sem: &'a Semaphore) -> SemaphoreGuard<'a> {
119        SemaphoreGuard { sem }
120    }
121}
122
123impl Drop for SemaphoreGuard<'_> {
124    fn drop(&mut self) {
125        let _ = self.sem.release();
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    
132    use std::io::{ErrorKind, Result};
133
134    use ::function_name::named;
135
136    use crate::Semaphore;
137
138    macro_rules! test_semaphore {
139        ($capacity:expr) => {{
140            let sem = Semaphore::open(function_name!(), $capacity)?;
141            sem.unlink()?;
142            sem
143        }};
144    }
145
146    #[test]
147    #[named]
148    fn creates_and_closes() -> Result<()> {
149        let sem = test_semaphore!(0);
150        sem.close()?;
151        Ok(())
152    }
153
154    #[test]
155    #[named]
156    fn creates_with_initial_value() -> Result<()> {
157        let sem = test_semaphore!(1);
158        assert_eq!(sem.value()?, 1);
159        Ok(())
160    }
161
162    #[test]
163    #[named]
164    fn invalid_name_fails() -> Result<()> {
165        let result = Semaphore::open("\0invalid", 0)
166            .err().unwrap();
167        assert_eq!(result.kind(), ErrorKind::InvalidInput);
168        Ok(())
169    }
170
171    #[test]
172    #[named]
173    fn decrements_and_increments() -> Result<()> {
174        let sem = test_semaphore!(1);
175        {
176            let _ = sem.access()?;
177        }
178        Ok(())
179    }
180
181    #[test]
182    #[named]
183    fn try_access_succeeds_with_capacity() -> Result<()> {
184        let sem = test_semaphore!(1);
185        {
186            let _ = sem.try_access()?;
187        }
188        Ok(())
189    }
190
191    #[test]
192    #[named]
193    fn try_access_fails_without_capacity() -> Result<()> {
194        let sem = test_semaphore!(0);
195        let result = sem.try_access().err().unwrap();
196        assert_eq!(result.kind(), ErrorKind::WouldBlock);
197        Ok(())
198    }
199
200    #[test]
201    #[named]
202    fn value_returns_initial_capacity() -> Result<()> {
203        let sem = test_semaphore!(2);
204        assert_eq!(sem.value()?, 2usize);
205        Ok(())
206    }
207
208    #[test]
209    #[named]
210    fn sems_with_same_name_share_value() -> Result<()> {
211        let sem_name = function_name!();
212        let sem = Semaphore::open(sem_name, 1)?;
213        assert_eq!(sem.value()?, 1);
214        let handle = std::thread::spawn(move || {
215            let sem = Semaphore::open(sem_name, 0).expect("failed to open");
216            assert_eq!(sem.value().expect("failed to get value"), 1);
217        });
218        let result = handle.join();
219        sem.unlink()?;
220        result.expect("failed to join thread");
221        assert_eq!(sem.value()?, 1);
222
223        Ok(())
224    }
225}
226