Skip to content

Commit 9ab4f77

Browse files
Futex
1 parent e756c96 commit 9ab4f77

File tree

3 files changed

+321
-0
lines changed

3 files changed

+321
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ This project adheres to [Semantic Versioning](https://semver.org/).
55

66
## [Unreleased] - ReleaseDate
77
### Added
8+
9+
- Added futex interface.
10+
([#1907](https://github.com/nix-rust/nix/pull/1907))
811
- Add `PF_ROUTE` to `SockType` on macOS, iOS, all of the BSDs, Fuchsia, Haiku, Illumos.
912
([#1867](https://github.com/nix-rust/nix/pull/1867))
1013
- Added `nix::ucontext` module on `aarch64-unknown-linux-gnu`.

src/sys/futex.rs

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
use crate::{Errno, Result};
2+
use libc::{syscall, SYS_futex};
3+
use std::convert::TryFrom;
4+
use std::os::unix::io::{FromRawFd, OwnedFd};
5+
use std::time::Duration;
6+
7+
fn timespec(duration: Duration) -> libc::timespec {
8+
let tv_sec = duration.as_secs().try_into().unwrap();
9+
let tv_nsec = duration.subsec_nanos().try_into().unwrap();
10+
libc::timespec { tv_sec, tv_nsec }
11+
}
12+
13+
fn unwrap_or_null<T>(option: Option<&T>) -> *const T {
14+
match option {
15+
Some(t) => t,
16+
None => std::ptr::null(),
17+
}
18+
}
19+
20+
/// Fast user-space locking.
21+
///
22+
/// By default we presume the futex is not process-private, that is, it is used across processes. If
23+
/// you know it is process-private you can set `PRIVATE` to `true` which allows some additional
24+
/// optimizations.
25+
/// ```
26+
/// # use nix::{sys::futex::Futex,errno::Errno};
27+
/// # use std::time::{Instant, Duration};
28+
/// const TIMEOUT: Duration = Duration::from_millis(500);
29+
/// const DELTA: Duration = Duration::from_millis(100);
30+
/// let futex: Futex = Futex::new(0);
31+
///
32+
/// // If the value of the futex is 0, wait for wake. Since the value is 0 and no wake occurs,
33+
/// // we expect the timeout will pass.
34+
/// {
35+
/// let instant = Instant::now();
36+
/// assert_eq!(futex.wait(0, Some(TIMEOUT)),Err(Errno::ETIMEDOUT));
37+
/// assert!(instant.elapsed() > TIMEOUT);
38+
/// }
39+
/// // If the value of the futex is 1, wait for wake. Since the value is 0, not 1, this will
40+
/// // return immediately.
41+
/// {
42+
/// let instant = Instant::now();
43+
/// assert_eq!(futex.wait(1, Some(TIMEOUT)),Err(Errno::EAGAIN));
44+
/// assert!(instant.elapsed() < DELTA);
45+
/// }
46+
///
47+
/// {
48+
/// let futex = std::sync::Arc::new(futex);
49+
/// let futex_clone = futex.clone();
50+
/// let instant = Instant::now();
51+
/// std::thread::spawn(move || {
52+
/// std::thread::sleep(TIMEOUT); // Sleep for `timeout`
53+
/// assert_eq!(futex_clone.wake(1),Ok(1)); // Wake 1 waiting thread
54+
/// });
55+
/// assert_eq!(futex.wait(0, Some(2 * TIMEOUT)),Ok(())); // Wait on wake
56+
/// assert!(instant.elapsed() > TIMEOUT && instant.elapsed() < TIMEOUT + DELTA); // Assert waited ~`timeout`
57+
/// }
58+
/// ```
59+
#[derive(Debug, Clone, Copy, Default)]
60+
pub struct Futex<const PRIVATE: bool = false>(u32);
61+
impl<const PRIVATE: bool> Futex<PRIVATE> {
62+
const MASK: i32 = if PRIVATE { libc::FUTEX_PRIVATE_FLAG } else { 0 };
63+
/// Create new futex with word value of `val`.
64+
pub fn new(val: u32) -> Self {
65+
Self(val)
66+
}
67+
/// If the value of the futex:
68+
/// - `== val`, the thread sleeps waiting for a [`Futex::wake`] call, in this case this thread
69+
/// is considered a waiter on this futex.
70+
/// - `!= val`, then `Err` with [`Errno::EAGAIN`] is immediately returned.
71+
///
72+
/// If the timeout is:
73+
/// - `Some(_)` it specifies a timeout for the wait.
74+
/// - `None` it will block indefinitely.
75+
///
76+
/// Wraps [`libc::FUTEX_WAIT`].
77+
pub fn wait(&self, val: u32, timeout: Option<Duration>) -> Result<()> {
78+
let timespec = timeout.map(timespec);
79+
let timespec_ptr = unwrap_or_null(timespec.as_ref());
80+
81+
let res = unsafe {
82+
syscall(
83+
SYS_futex,
84+
&self.0,
85+
Self::MASK | libc::FUTEX_WAIT,
86+
val,
87+
timespec_ptr,
88+
)
89+
};
90+
Errno::result(res).map(drop)
91+
}
92+
/// Wakes at most `val` waiters.
93+
///
94+
/// - `val == 1` wakes a single waiter.
95+
/// - `val == u32::MAX` wakes all waiters.
96+
///
97+
/// No guarantee is provided about which waiters are awoken. A waiter with a higher scheduling
98+
/// priority is not guaranteed to be awoken in preference to a waiter with a lower priority.
99+
///
100+
/// Wraps [`libc::FUTEX_WAKE`].
101+
pub fn wake(&self, val: u32) -> Result<u32> {
102+
let res = unsafe {
103+
syscall(SYS_futex, &self.0, Self::MASK | libc::FUTEX_WAKE, val)
104+
};
105+
Errno::result(res).map(|x| u32::try_from(x).unwrap())
106+
}
107+
/// Creates a file descriptor associated with the futex.
108+
///
109+
/// When [`Futex::wake`] is performed on the futex this file indicates being readable with
110+
/// `select`, `poll` and `epoll`.
111+
///
112+
/// The file descriptor can be used to obtain asynchronous notifications: if val is nonzero,
113+
/// then, when another process or thread executes a FUTEX_WAKE, the caller will receive the
114+
/// signal number that was passed in val.
115+
///
116+
/// **Because it was inherently racy, this is unsupported from Linux 2.6.26 onward.**
117+
///
118+
/// Wraps [`libc::FUTEX_FD`].
119+
pub fn fd(&self, val: u32) -> Result<OwnedFd> {
120+
let res = unsafe {
121+
syscall(SYS_futex, &self.0, Self::MASK | libc::FUTEX_WAKE, val)
122+
};
123+
124+
// On a 32 bit arch `x` will be an `i32` and will trigger this lint.
125+
#[allow(clippy::useless_conversion)]
126+
Errno::result(res)
127+
.map(|x| unsafe { OwnedFd::from_raw_fd(i32::try_from(x).unwrap()) })
128+
}
129+
/// [`Futex::cmp_requeue`] without the check being made using `val3`.
130+
///
131+
/// Wraps [`libc::FUTEX_REQUEUE`].
132+
pub fn requeue(&self, val: u32, val2: u32, uaddr2: &Self) -> Result<u32> {
133+
let res = unsafe {
134+
syscall(
135+
SYS_futex,
136+
&self.0,
137+
Self::MASK | libc::FUTEX_CMP_REQUEUE,
138+
val,
139+
val2,
140+
&uaddr2.0,
141+
)
142+
};
143+
Errno::result(res).map(|x| u32::try_from(x).unwrap())
144+
}
145+
/// Wakes `val` waiters, moving remaining (up to `val2`) waiters to `uaddr2`.
146+
///
147+
/// If the value of this futex `== val3` returns `Err` with [`Errno::EAGAIN`].
148+
///
149+
/// Typical values to specify for `val` are `0` or `1` (Specifying `u32::MAX` makes the
150+
/// [`Futex::cmp_requeue`] equivalent to [`Futex::wake`]).
151+
///
152+
/// Typical values to specify for `val2` are `1` or `u32::MAX` (Specifying `0` makes
153+
/// [`Futex::cmp_requeue`] equivalent to [`Futex::wait`]).
154+
///
155+
/// Wraps [`libc::FUTEX_CMP_REQUEUE`].
156+
pub fn cmp_requeue(
157+
&self,
158+
val: u32,
159+
val2: u32,
160+
uaddr2: &Self,
161+
val3: u32,
162+
) -> Result<u32> {
163+
let res = unsafe {
164+
syscall(
165+
SYS_futex,
166+
&self.0,
167+
Self::MASK | libc::FUTEX_CMP_REQUEUE,
168+
val,
169+
val2,
170+
&uaddr2.0,
171+
val3,
172+
)
173+
};
174+
Errno::result(res).map(|x| u32::try_from(x).unwrap())
175+
}
176+
/// Wraps [`libc::FUTEX_WAKE_OP`].
177+
pub fn wake_op(
178+
&self,
179+
val: u32,
180+
val2: u32,
181+
uaddr2: &Self,
182+
val3: u32,
183+
) -> Result<u32> {
184+
let res = unsafe {
185+
syscall(
186+
SYS_futex,
187+
&self.0,
188+
Self::MASK | libc::FUTEX_WAKE_OP,
189+
val,
190+
val2,
191+
&uaddr2.0,
192+
val3,
193+
)
194+
};
195+
Errno::result(res).map(|x| u32::try_from(x).unwrap())
196+
}
197+
/// Wraps [`libc::FUTEX_WAIT_BITSET`].
198+
pub fn wait_bitset(
199+
&self,
200+
val: u32,
201+
timeout: Option<Duration>,
202+
val3: u32,
203+
) -> Result<()> {
204+
let timespec = timeout.map(timespec);
205+
let timespec_ptr = unwrap_or_null(timespec.as_ref());
206+
207+
let res = unsafe {
208+
syscall(
209+
SYS_futex,
210+
&self.0,
211+
Self::MASK | libc::FUTEX_WAIT_BITSET,
212+
val,
213+
timespec_ptr,
214+
val3,
215+
)
216+
};
217+
Errno::result(res).map(drop)
218+
}
219+
/// Wraps [`libc::FUTEX_WAKE_BITSET`].
220+
pub fn wake_bitset(&self, val: u32, val3: u32) -> Result<u32> {
221+
let res = unsafe {
222+
syscall(SYS_futex, &self.0, libc::FUTEX_WAKE_BITSET, val, val3)
223+
};
224+
Errno::result(res).map(|x| u32::try_from(x).unwrap())
225+
}
226+
/// Wraps [`libc::FUTEX_LOCK_PI`].
227+
pub fn lock_pi(&self, timeout: Option<Duration>) -> Result<()> {
228+
let timespec = timeout.map(timespec);
229+
let timespec_ptr = unwrap_or_null(timespec.as_ref());
230+
231+
let res = unsafe {
232+
syscall(
233+
SYS_futex,
234+
&self.0,
235+
Self::MASK | libc::FUTEX_LOCK_PI,
236+
timespec_ptr,
237+
)
238+
};
239+
Errno::result(res).map(drop)
240+
}
241+
/// Wraps [`libc::FUTEX_LOCK_PI2`].
242+
#[cfg(target_os = "linux")]
243+
pub fn lock_pi2(&self, timeout: Option<Duration>) -> Result<()> {
244+
let timespec = timeout.map(timespec);
245+
let timespec_ptr = unwrap_or_null(timespec.as_ref());
246+
247+
let res = unsafe {
248+
syscall(
249+
SYS_futex,
250+
&self.0,
251+
Self::MASK | libc::FUTEX_LOCK_PI2,
252+
timespec_ptr,
253+
)
254+
};
255+
Errno::result(res).map(drop)
256+
}
257+
/// Wraps [`libc::FUTEX_TRYLOCK_PI`].
258+
pub fn trylock_pi(&self) -> Result<()> {
259+
let res = unsafe {
260+
syscall(SYS_futex, &self.0, Self::MASK | libc::FUTEX_TRYLOCK_PI)
261+
};
262+
Errno::result(res).map(drop)
263+
}
264+
/// `libc::FUTEX_UNLOCK_PI`
265+
pub fn unlock_pi(&self) -> Result<()> {
266+
let res = unsafe {
267+
syscall(SYS_futex, &self.0, Self::MASK | libc::FUTEX_UNLOCK_PI)
268+
};
269+
Errno::result(res).map(drop)
270+
}
271+
/// Wraps [`libc::FUTEX_CMP_REQUEUE_PI`].
272+
pub fn cmp_requeue_pi(
273+
&self,
274+
val: u32,
275+
val2: u32,
276+
uaddr2: &Self,
277+
val3: u32,
278+
) -> Result<u32> {
279+
let res = unsafe {
280+
syscall(
281+
SYS_futex,
282+
&self.0,
283+
Self::MASK | libc::FUTEX_CMP_REQUEUE_PI,
284+
val,
285+
val2,
286+
&uaddr2.0,
287+
val3,
288+
)
289+
};
290+
Errno::result(res).map(|x| u32::try_from(x).unwrap())
291+
}
292+
/// Wraps [`libc::FUTEX_WAIT_REQUEUE_PI`].
293+
pub fn wait_requeue_pi(
294+
&self,
295+
val: u32,
296+
timeout: Option<Duration>,
297+
uaddr2: &Self,
298+
) -> Result<()> {
299+
let timespec = timeout.map(timespec);
300+
let timespec_ptr = unwrap_or_null(timespec.as_ref());
301+
302+
let res = unsafe {
303+
syscall(
304+
SYS_futex,
305+
&self.0,
306+
Self::MASK | libc::FUTEX_WAIT_REQUEUE_PI,
307+
val,
308+
timespec_ptr,
309+
&uaddr2.0,
310+
)
311+
};
312+
Errno::result(res).map(drop)
313+
}
314+
}

src/sys/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,7 @@ feature! {
226226
#![feature = "time"]
227227
pub mod timer;
228228
}
229+
230+
/// Fast user-space locking.
231+
#[cfg(any(target_os = "android", target_os = "linux"))]
232+
pub mod futex;

0 commit comments

Comments
 (0)