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