-
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
Conversation
Any plans to extend this to the other platforms (besides WASM) using TLS? |
Yes, but I am not sure if Haiku is considered a part of UNIX family by Rust. Also current implementation has a bug, which I'll fix later. |
I think I've finished what I wanted to do with non-WASM targets. Result is a bit more complex than that I initially had in mind, but oh well. :) There are certain code duplications, but due to subtle differences I don't see how they can be abstracted nicely. I think orderings are correct, but it's better to double check. I guess we could remove TLS for WASM targets as well and replace them with "atomics" (which in future may become true atomics) using a similar approach. |
BTW maybe we can remove |
Good call, but lets keep that in a separate PR. You can test the WASM stuff by following the CI script. |
@newpavlov, I would say you've done a good job on this, but I'm still not keen to accept it. The reason is that making heavy use of low-level tools (atomics, bit-ops) makes the code considerably less readable, which makes the code:
Also, if we are going to take this approach, it would be good to have someone who really understands atomic synchronisation primitives to review this code. I'm not saying a definite no to this, but I would like us to look at alternatives first (e.g. an (I guess I also owe you an apology for the slow review and initially-positive comments; I've had a busy week.) |
While I agree that this code should be carefully reviewed, I think that increasing reader-facing complexity a bit, but getting in return the most efficient implementation for given platform is a not bad trade-off for foundational low-level library. Regarding |
It may be best then to leave this PR unmerged for now (there's no reason optimisations cannot land in a point release). Would you mind making a separate PR for the module changes + wasm-bindgen clean-up, then rebasing this? |
If you don't want to delay What do you mean by module changes? Also how about bumping MSRV to 1.31 and migrating to 2018 edition? I think 3 release difference is not a big deal, especially considering that upcoming Debian stable will package Rust 1.32. |
You merged four modules into one in this PR; I think this is easy to do without the other changes here? Also you renamed 'solarish'; I don't really care about the name but it would be nicer if this PR didn't change it. If it makes the code significantly nicer, go ahead and use 1.31. There seems to be very little interest in support for old compilers. |
I think this could be tidied up a little if you still want to do so, e.g. avoid renaming |
Done! Looks like CI failure is due to rust-lang/rust#59731. To be safe it would be nice to ask someone to review atomics usage. For example right now I use |
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.
I did not have time to check all implementations, but hopefully those that I have reviewed should give you an idea of how to review the rest.
As a general comment, since lock-free code can be a pain to maintain, it might be a good idea to somehow centralize all the tricky synchronization in a single place that is shared by all implementations.
const STATE_USE_FD: usize = 1 << 2; | ||
|
||
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { | ||
let state = RNG_STATE.load(Ordering::Acquire); |
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...
- Some state has indeed been initialized
- You need to synchronize with extra state besides the atomic value itself (e.g. another atomic value)
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.
); | ||
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 comment
The 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.
fn init(dest: &mut [u8]) -> Result<(), Error> { | ||
match use_syscall(&mut []) { | ||
Ok(()) => { | ||
RNG_STATE.store(STATE_USE_SYSCALL, Ordering::Release); |
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.
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.
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 comment
The 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 RNG_FD
loaded by the reader is at least as recent as that of RNG_STATE
in this case.
use_init(f, getrandom_init, |source| getrandom_fill(source, dest)) | ||
}) | ||
|
||
let state = RNG_STATE.load(Ordering::Acquire); |
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.
I don't think you need Acquire here, since as far as I can see you're not synchronizing any shared state besides the value of the atomic variable itself...
} else { | ||
STATE_INIT_DONE | ||
}, | ||
Ordering::Release, |
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.
...and thus, Release should not be needed here either.
@HadrienG2
I've tried to think about how it can be done, bit I couldn't find good ideas, each case has a slightly different requirements (some platforms got merged into @dhardy |
I think it still needs a very careful review, and it would be worth trying to address @HadrienG2's points before this. Alternatively, we could still avoid TLS by using regular mutexes. Not as efficient perhaps, but I don't think that is so important here, and it's much easier to ensure correctness. (In this case I'm not sure if it's better to use |
I think it will be better to do it in a separate issue/PR, in other words let's introduce a conservative version first and then work on relaxing it if needed. Though I think it may be better to return to |
Closes: #13