-
Notifications
You must be signed in to change notification settings - Fork 183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove TLS #14
Remove TLS #14
Changes from all commits
58988b8
194beb5
2f87b9c
1cb77a6
9852c8b
6ab631c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,78 +10,103 @@ | |
extern crate std; | ||
|
||
use crate::Error; | ||
use crate::utils::use_init; | ||
use std::{thread_local, io::{self, Read}, fs::File}; | ||
use core::cell::RefCell; | ||
use core::num::NonZeroU32; | ||
use core::sync::atomic::{AtomicBool, Ordering}; | ||
use std::fs::File; | ||
use std::io::Read; | ||
use std::num::NonZeroU32; | ||
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering}; | ||
use std::os::unix::io::{AsRawFd, FromRawFd}; | ||
use std::{io, mem}; | ||
|
||
// This flag tells getrandom() to return EAGAIN instead of blocking. | ||
// replace with AtomicU8 on stabilization and MSRV bump | ||
static RNG_STATE: AtomicUsize = AtomicUsize::new(0); | ||
// replace with AtomicI32 on stabilization and MSRV bump | ||
static RNG_FD: AtomicIsize = AtomicIsize::new(-1); | ||
|
||
const STATE_INIT_ONGOING: usize = 1 << 0; | ||
const STATE_USE_SYSCALL: usize = 1 << 1; | ||
const STATE_USE_FD: usize = 1 << 2; | ||
const GRND_NONBLOCK: libc::c_uint = 0x0001; | ||
static RNG_INIT: AtomicBool = AtomicBool::new(false); | ||
|
||
enum RngSource { | ||
GetRandom, | ||
Device(File), | ||
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { | ||
let state = RNG_STATE.load(Ordering::Acquire); | ||
if state & STATE_USE_SYSCALL != 0 { | ||
use_syscall(dest, true) | ||
} else if state & STATE_USE_FD != 0 { | ||
use_fd(dest) | ||
} else { | ||
init_loop(dest) | ||
} | ||
} | ||
|
||
fn init_loop(dest: &mut [u8]) -> Result<(), Error> { | ||
loop { | ||
let state = RNG_STATE.fetch_or(STATE_INIT_ONGOING, Ordering::AcqRel); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Release ordering is about allowing clients who read STATE_INIT_ONGOING with Acquire ordering to synchronize with other shared state that you modified before writing to this variable. I do not think that it is useful here. If this is just a state flag, even Relaxed could be enough, as long as you use an Acquire fence as discussed above where necessary. |
||
|
||
if state & STATE_INIT_ONGOING != 0 { | ||
std::thread::yield_now(); | ||
continue; | ||
} | ||
return if state & STATE_USE_SYSCALL != 0 { | ||
use_syscall(dest, true) | ||
} else if state & STATE_USE_FD != 0 { | ||
use_fd(dest) | ||
} else { | ||
init(dest) | ||
}; | ||
} | ||
} | ||
|
||
thread_local!( | ||
static RNG_SOURCE: RefCell<Option<RngSource>> = RefCell::new(None); | ||
); | ||
fn init(dest: &mut [u8]) -> Result<(), Error> { | ||
match use_syscall(&mut [], false) { | ||
Ok(()) => { | ||
RNG_STATE.store(STATE_USE_SYSCALL, Ordering::Release); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for Release ordering if you have no writes to other shared memory locations that you want to share with other threads before the atomic store. |
||
use_syscall(dest, true) | ||
}, | ||
Err(err) if err.code().get() as i32 == libc::ENOSYS => { | ||
match init_fd() { | ||
Ok(fd) => { | ||
RNG_FD.store(fd as isize, Ordering::SeqCst); | ||
RNG_STATE.store(STATE_USE_FD, Ordering::SeqCst); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Relaxed followed by Release on this side, and an Acquire fence on the reader's side, are enough to guarantee that the value of |
||
use_fd(dest) | ||
}, | ||
Err(err) => { | ||
RNG_STATE.store(0, Ordering::Release); | ||
Err(err.into()) | ||
} | ||
} | ||
}, | ||
Err(err) => Err(err), | ||
} | ||
} | ||
|
||
fn syscall_getrandom(dest: &mut [u8], block: bool) -> Result<(), io::Error> { | ||
fn init_fd() -> io::Result<i32> { | ||
// read one byte from "/dev/random" to ensure that OS RNG has initialized | ||
File::open("/dev/random")?.read_exact(&mut [0u8; 1])?; | ||
let f = File::open("/dev/urandom")?; | ||
let fd = f.as_raw_fd(); | ||
mem::forget(f); | ||
Ok(fd) | ||
} | ||
|
||
fn use_syscall(dest: &mut [u8], block: bool) -> Result<(), Error> { | ||
let flags = if block { 0 } else { GRND_NONBLOCK }; | ||
let ret = unsafe { | ||
libc::syscall(libc::SYS_getrandom, dest.as_mut_ptr(), dest.len(), flags) | ||
}; | ||
if ret < 0 || (ret as usize) != dest.len() { | ||
error!("Linux getrandom syscall failed with return value {}", ret); | ||
return Err(io::Error::last_os_error()); | ||
return Err(io::Error::last_os_error().into()); | ||
newpavlov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
Ok(()) | ||
} | ||
|
||
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { | ||
RNG_SOURCE.with(|f| { | ||
use_init(f, | ||
|| { | ||
let s = if is_getrandom_available() { | ||
RngSource::GetRandom | ||
} else { | ||
// read one byte from "/dev/random" to ensure that | ||
// OS RNG has initialized | ||
if !RNG_INIT.load(Ordering::Relaxed) { | ||
File::open("/dev/random")?.read_exact(&mut [0u8; 1])?; | ||
RNG_INIT.store(true, Ordering::Relaxed) | ||
} | ||
RngSource::Device(File::open("/dev/urandom")?) | ||
}; | ||
Ok(s) | ||
}, |f| { | ||
match f { | ||
RngSource::GetRandom => syscall_getrandom(dest, true), | ||
RngSource::Device(f) => f.read_exact(dest), | ||
}.map_err(From::from) | ||
}) | ||
}) | ||
} | ||
|
||
fn is_getrandom_available() -> bool { | ||
use std::sync::{Once, ONCE_INIT}; | ||
|
||
static CHECKER: Once = ONCE_INIT; | ||
static AVAILABLE: AtomicBool = AtomicBool::new(false); | ||
|
||
CHECKER.call_once(|| { | ||
let mut buf: [u8; 0] = []; | ||
let available = match syscall_getrandom(&mut buf, false) { | ||
Ok(()) => true, | ||
Err(err) => err.raw_os_error() != Some(libc::ENOSYS), | ||
}; | ||
AVAILABLE.store(available, Ordering::Relaxed); | ||
}); | ||
|
||
AVAILABLE.load(Ordering::Relaxed) | ||
fn use_fd(dest: &mut [u8]) -> Result<(), Error> { | ||
unsafe { | ||
let fd = RNG_FD.load(Ordering::Acquire) as i32; | ||
let mut f = File::from_raw_fd(fd); | ||
f.read_exact(dest)?; | ||
mem::forget(f); | ||
} | ||
Ok(()) | ||
} | ||
|
||
#[inline(always)] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may be able to reduce synchronization overhead by using a Relaxed load, and only using an Acquire fence after the fact if...
But cross-check that this is worthwhile with a benchmark on a relaxed-memory architecture (Arm, Power...), because compilers are less skilled at optimizing fences than they are at optimizing ordered atomic ops.