Skip to content

Commit 3b0b67b

Browse files
committed
use_file: replace mutex with nanosleep-based loop
1 parent b7bba16 commit 3b0b67b

File tree

1 file changed

+61
-51
lines changed

1 file changed

+61
-51
lines changed

src/use_file.rs

+61-51
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ use crate::{
44
Error,
55
};
66
use core::{
7-
cell::UnsafeCell,
87
ffi::c_void,
98
mem::MaybeUninit,
10-
sync::atomic::{AtomicUsize, Ordering::Relaxed},
9+
sync::atomic::{AtomicUsize, Ordering},
1110
};
1211

1312
/// For all platforms, we use `/dev/urandom` rather than `/dev/random`.
@@ -18,6 +17,7 @@ use core::{
1817
/// - On Haiku and QNX Neutrino they are identical.
1918
const FILE_PATH: &[u8] = b"/dev/urandom\0";
2019
const FD_UNINIT: usize = usize::MAX;
20+
const FD_ONGOING_INIT: usize = usize::MAX - 1;
2121

2222
// Do not inline this when it is the fallback implementation, but don't mark it
2323
// `#[cold]` because it is hot when it is actually used.
@@ -35,42 +35,70 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
3535
fn get_rng_fd() -> Result<libc::c_int, Error> {
3636
static FD: AtomicUsize = AtomicUsize::new(FD_UNINIT);
3737

38-
fn get_fd() -> Option<libc::c_int> {
39-
match FD.load(Relaxed) {
40-
FD_UNINIT => None,
41-
val => Some(val as libc::c_int),
42-
}
43-
}
44-
4538
#[cold]
46-
fn get_fd_locked() -> Result<libc::c_int, Error> {
47-
// SAFETY: We use the mutex only in this method, and we always unlock it
48-
// before returning, making sure we don't violate the pthread_mutex_t API.
49-
static MUTEX: Mutex = Mutex::new();
50-
unsafe { MUTEX.lock() };
51-
let _guard = DropGuard(|| unsafe { MUTEX.unlock() });
52-
53-
if let Some(fd) = get_fd() {
54-
return Ok(fd);
39+
fn init_or_wait_fd() -> Result<libc::c_int, Error> {
40+
// Maximum sleep time (~268 milliseconds)
41+
let max_sleep_ns = 1 << 28;
42+
// Starting sleep time (~4 microseconds)
43+
let mut timeout_ns = 1 << 12;
44+
loop {
45+
match FD.load(Ordering::Acquire) {
46+
FD_UNINIT => {}
47+
FD_ONGOING_INIT => {
48+
let rqtp = libc::timespec {
49+
tv_sec: 0,
50+
tv_nsec: timeout_ns,
51+
};
52+
let mut rmtp = libc::timespec {
53+
tv_sec: 0,
54+
tv_nsec: 0,
55+
};
56+
unsafe {
57+
libc::nanosleep(&rqtp, &mut rmtp);
58+
}
59+
if timeout_ns < max_sleep_ns {
60+
timeout_ns *= 2;
61+
}
62+
continue;
63+
}
64+
val => return Ok(val as libc::c_int),
65+
}
66+
67+
let xch_res = FD.compare_exchange_weak(
68+
FD_UNINIT,
69+
FD_ONGOING_INIT,
70+
Ordering::AcqRel,
71+
Ordering::Relaxed,
72+
);
73+
if xch_res.is_err() {
74+
continue;
75+
}
76+
77+
let res = open_fd();
78+
let val = match res {
79+
Ok(fd) => fd as usize,
80+
Err(_) => FD_UNINIT,
81+
};
82+
FD.store(val, Ordering::Release);
83+
return res;
5584
}
85+
}
5686

87+
fn open_fd() -> Result<libc::c_int, Error> {
5788
// On Linux, /dev/urandom might return insecure values.
5889
#[cfg(any(target_os = "android", target_os = "linux"))]
5990
wait_until_rng_ready()?;
6091

6192
let fd = open_readonly(FILE_PATH)?;
6293
// The fd always fits in a usize without conflicting with FD_UNINIT.
63-
debug_assert!(fd >= 0 && (fd as usize) < FD_UNINIT);
64-
FD.store(fd as usize, Relaxed);
94+
debug_assert!(fd >= 0 && (fd as usize) < FD_ONGOING_INIT);
6595

6696
Ok(fd)
6797
}
6898

69-
// Use double-checked locking to avoid acquiring the lock if possible.
70-
if let Some(fd) = get_fd() {
71-
Ok(fd)
72-
} else {
73-
get_fd_locked()
99+
match FD.load(Ordering::Relaxed) {
100+
FD_UNINIT | FD_ONGOING_INIT => init_or_wait_fd(),
101+
val => Ok(val as libc::c_int),
74102
}
75103
}
76104

@@ -104,6 +132,14 @@ fn get_rng_fd() -> Result<libc::c_int, Error> {
104132
// libsodium uses `libc::poll` similarly to this.
105133
#[cfg(any(target_os = "android", target_os = "linux"))]
106134
fn wait_until_rng_ready() -> Result<(), Error> {
135+
struct DropGuard<F: FnMut()>(F);
136+
137+
impl<F: FnMut()> Drop for DropGuard<F> {
138+
fn drop(&mut self) {
139+
self.0()
140+
}
141+
}
142+
107143
let fd = open_readonly(b"/dev/random\0")?;
108144
let mut pfd = libc::pollfd {
109145
fd,
@@ -128,29 +164,3 @@ fn wait_until_rng_ready() -> Result<(), Error> {
128164
}
129165
}
130166
}
131-
132-
struct Mutex(UnsafeCell<libc::pthread_mutex_t>);
133-
134-
impl Mutex {
135-
const fn new() -> Self {
136-
Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER))
137-
}
138-
unsafe fn lock(&self) {
139-
let r = libc::pthread_mutex_lock(self.0.get());
140-
debug_assert_eq!(r, 0);
141-
}
142-
unsafe fn unlock(&self) {
143-
let r = libc::pthread_mutex_unlock(self.0.get());
144-
debug_assert_eq!(r, 0);
145-
}
146-
}
147-
148-
unsafe impl Sync for Mutex {}
149-
150-
struct DropGuard<F: FnMut()>(F);
151-
152-
impl<F: FnMut()> Drop for DropGuard<F> {
153-
fn drop(&mut self) {
154-
self.0()
155-
}
156-
}

0 commit comments

Comments
 (0)