-
Notifications
You must be signed in to change notification settings - Fork 256
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
Relax atomic orderings #520
Conversation
Closes #509 In talking with some other developers some concern was presented over whether or not a write to |
I'm not sure that this new ordering is valid, but does it really matter? The logger is set at most once, usually during startup, personally I don't think the performance concerns at that stage outweigh getting this wrong... |
The initialization of the logger wasn't really of concern here, the A couple remarks on that: this doesn't change a whole lot. Using If the primary concern here is correctness over performance, then I'd personally suggest not using a home-brew If you want to close this that's fine. My two cents is that in its current state the once implementation here is non-optimal (and this PR doesn't fix the aforementioned unbounded spin loop), and that if you want correctness then a better solution would be to rely on a 3rd party once crate with more thorough auditing than to simply make all orderings EDIT: I forgot to mention, if the correctness of this implementation was of primary concern, then I could add loom tests which would give pretty substantial reassurance that the orderings are correct. |
@Cassy343 I'm familiar with atomic ordering, that wasn't my point. My point was that the ordering is tricky to get right (even if you following docs precisely) and that I don't think it's worth it for the setup phase of a program. So, I propose to reduce this pr to just change the atomic ordering of the Regarding the ordering |
That ordering should not be reduced to relaxed. Even if we properly document it, if one thread initializes the logger and another concurrently tries to access the logger, then that's undefined behavior which is not allowed to occur in safe code, so that function would need to be marked as unsafe. I'd also wager that a non-trivial number of users are initializing the logger after spinning up threads due to libraries such as tokio and rocket providing macros that wrap your |
How do you think this is possible?
I disagree. I think if you put a |
That is not what |
To add on to @chorman0773's point, here is a (correctly failing) loom test demonstrating that fn main() {
use loom::{sync::atomic::{AtomicUsize, Ordering, fence}, cell::UnsafeCell, thread};
use std::sync::Arc;
loom::model(|| {
let number = Arc::new(UnsafeCell::new(0i32));
let state = Arc::new(AtomicUsize::new(0));
thread::spawn({
let number = Arc::clone(&number);
let state = Arc::clone(&state);
move || {
if state.load(Ordering::Relaxed) == 2 {
number.with(|ptr| assert_eq!(unsafe { *ptr }, 1));
}
}
});
thread::spawn(move || {
match state.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst) {
Ok(0) => {
number.with_mut(|ptr| unsafe { *ptr = 1 });
state.store(2, Ordering::SeqCst);
},
_ => unreachable!()
}
fence(Ordering::SeqCst);
});
});
} |
@Cassy343 I don't use Loom, but that's not exactly what I had in my. I was thinking the following. fn main() {
use loom::{sync::atomic::{AtomicUsize, Ordering, fence}, cell::UnsafeCell, thread};
use std::sync::Arc;
loom::model(|| {
let number = Arc::new(UnsafeCell::new(0i32));
let state = Arc::new(AtomicUsize::new(0));
thread::spawn({
let number = Arc::clone(&number);
let state = Arc::clone(&state);
move || {
if state.load(Ordering::Relaxed) == 2 {
number.with(|ptr| assert_eq!(unsafe { *ptr }, 1));
}
}
});
thread::spawn(move || {
match state.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst) {
Ok(0) => {
number.with_mut(|ptr| unsafe { *ptr = 1 });
fence(Ordering::SeqCst); // Sync the write above.
state.store(2, Ordering::SeqCst);
},
_ => unreachable!()
}
});
});
} |
But I think you guys are right and we still need something this proposed |
The impact is not limited to the startup phase, incurring unnecessary overhead every time the log!() is used, and SeqCst is a very expensive operation on some platforms. pub fn logger() -> &'static dyn Log {
if STATE.load(Ordering::SeqCst) != INITIALIZED { |
Thanks for the PR @Cassy343! I think one of your earlier points about not using a home-grown once cell for this is pretty valid, and once |
|
@KodrAus Maybe it's a good idea to use |
@NobodyXu Ah we don’t actually tie our MSRV to what’s in Debian. We just lined them up this time because there happened to be a release coming up, but I don’t expect us to stay tied to 1.60 for the next few years. We avoid adding any required dependencies to |
Wouldn't that break users who compile their programs on debian stable or make it harder for debian to package rust programs that depends on log?
If you don't want to tie log msrv bump to debian release, then I suppose it will be safe to do it in 6-9 months of time? |
…lang#520) to convert all tracing spans and events to log. Signed-off-by: Jiahao XU <[email protected]>
Problem is the |
In #610 we switched to using We could still accept the changes here to relax the orderings in the setup functions ( |
Well, that's what I get for trying to use GitHub's UI to merge branches. |
I've re-opened this as #632 so will close this PR. The discussion is all linked so we can find our way back to it in the future if needed. |
Currently this crate uses
SeqCst
orderings for all atomic operations related to logger initialization. At most these orderings would need to beAcqRel
since they only involve a single atomic. This PR relaxes all orderings as much as possible without introducing visibility issues.I also changed the initialization of the filter to be
LevelFilter::Off as usize
for clarity.