-
-
Notifications
You must be signed in to change notification settings - Fork 224
change Global to use Once #752
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
Conversation
if you're gonna do this you should at least use the most recent master considering #750 but also you'll need to be certain that this doesn't hurt performance i dont have time to look at this further rn tho |
7871677
to
37df2af
Compare
I can run some benchmarks, i guess? |
i ran a benchmark and the performance seems to be about the same. extern crate test;
#[bench]
fn x(b: &mut test::Bencher) {
b.iter(|| {
let x = Global::<String>::default();
for _ in 0..10 {
*x.lock() += "304.78";
}
})
} it could probably be a bit faster once get_mut_or_init drops. |
Thanks for the PR! This uses If you find concrete bugs with the current implementation, I'd prefer to address those directly and write regression tests for it, but I'm not really willing to introduce extra locking just so we can reuse standard facilities. Yes, I find it mindblowing too that something as basic as ergonomic global variables seems to be pioneering work in Rust 😉 |
wait i can swap it to OnceCell |
Please provide a proper rationale for the change. Your initial message doesn't even have a description. |
the rationale is to reduce the necessary maintenance surface, this makes the code much simpler. |
Some smaller disadvantages I see with your approach:
Btw, regarding several of your comments, also on Discord:
I could appreciate your changes a lot more without the snarky comments. You may not notice it, but the way you word things comes across as pretty arrogant. I would recommend to familiarize yourself with the existing code and design behind it, before just assuming it's garbage. Let's keep it technical from now on. Thanks. |
i believe this change doesn't make My bad, will do. |
godot-ffi/src/global.rs
Outdated
pub struct Global<T, F = fn() -> T> { | ||
// When needed, this could be changed to use RwLock and separate read/write guards. | ||
value: Mutex<InitState<T>>, | ||
value: Mutex<OnceCell<T>>, | ||
init_fn: F, | ||
} |
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.
The F
parameter is not necessary; new()
in its current form can only be invoked with a function pointer. I'd keep this a pure refactoring without changing the public API, we can discuss new features separately if needed.
godot-ffi/src/global.rs
Outdated
pub fn lock(&self) -> GlobalGuard<'_, T> { | ||
let mutex_guard = self | ||
.value | ||
.lock() | ||
.expect("Global<T> poisoned; a thread has panicked while holding a lock to it"); | ||
|
||
let guard = Self::ensure_init(mutex_guard, true) | ||
.unwrap_or_else(|| panic!("previous Global<T> initialization failed due to panic")); | ||
|
||
guard | ||
let guard = self.value.lock().unwrap_or_else(PoisonError::into_inner); | ||
guard.get_or_init(self.init_fn); | ||
// SAFETY: above line guarantees init | ||
unsafe { GlobalGuard::new_unchecked(guard) } |
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.
Are we sure the get_or_init()
isn't more expensive than the previous Self::ensure_init()
?
While it may not matter in average cases, it would be a pity to pessimize performance just to make our own life a bit easier.
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.
Nitpick, please also leave an empty line before //
comment blocks (here // SAFETY
).
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.
the get_or_init
here boils down to
if self.get().is_some() { return };
let value = f();
let slot = unsafe { &mut *self.inner.get() };
slot.insert(value);
which seems like an improvement from the previous ensure_init
fn.
and ive benchmarked it to be about the same speed.
godot-ffi/src/global.rs
Outdated
std::panic::catch_unwind(|| guard.get_or_init(self.init_fn)) | ||
.map_err(|_| GlobalLockError::InitFailed)?; | ||
Ok(unsafe { GlobalGuard::new_unchecked(guard) }) |
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.
It's not obvious why this is called again here, could you elaborate?
Also, unsafe
would need // SAFETY
, can be kept short like above.
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 hope ive clarified it now
godot-ffi/src/global.rs
Outdated
use std::{ | ||
cell::OnceCell, | ||
ops::{Deref, DerefMut}, | ||
sync::MutexGuard, | ||
}; |
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.
Imports
match mutex_guard.get() { | ||
Some(_) => Some(Self { mutex_guard }), | ||
_ => None, | ||
} |
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 can use map
here
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
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.
could you try to avoid just saying "no" like this? it isnt very helpful most of the time and we need to ask follow up questions anyway to understand more clearly. why doesn't map work?
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.
it results in an error because it moves the mutex guard into a FnOnce
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.
error[E0505]: cannot move out of `mutex_guard` because it is borrowed
--> godot-ffi/src/global.rs:114:35
|
113 | pub(super) fn new(mutex_guard: MutexGuard<'a, OnceCell<T>>) -> Option<Self> {
| ----------- binding `mutex_guard` declared here
114 | mutex_guard.get().map(|_| Self { mutex_guard })
| ----------- --- ^^^ ----------- move occurs due to use in closure
| | | |
| | | move out of `mutex_guard` occurs here
| | borrow later used by call
| borrow of `mutex_guard` occurs here
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 can do this though if you prefer
mutex_guard.get().is_some().then_some(Self { mutex_guard })
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 see what i have right now is basically an eager map
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.
Ok, then let's leave the match
. Can you add a comment mentioning that we can't use .get().map(...)
due to the guard remaining borrowed?
be74d96
to
d5c22f9
Compare
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-752 |
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.
Thanks! Few things left.
godot-ffi/src/global.rs
Outdated
// SAFETY: `get_or_init()` cant panic, therefore the object is guaranteed to be initialized. | ||
Ok(unsafe { GlobalGuard::new_unchecked(g) }) |
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.
The safety message is imprecise -- get_or_init
can panic, but a potential panic is caught above. Reaching this line implies that it didn't panic.
godot-ffi/src/global.rs
Outdated
// SAFETY: `get_or_init()` cant panic, therefore the object is guaranteed to be initialized. | ||
unsafe { GlobalGuard::new_unchecked(guard) } |
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.
Also here, get_or_init
can panic but the panic is propagated to the caller.
match mutex_guard.get() { | ||
Some(_) => Some(Self { mutex_guard }), | ||
_ => None, | ||
} |
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.
Ok, then let's leave the match
. Can you add a comment mentioning that we can't use .get().map(...)
due to the guard remaining borrowed?
45df6a7
to
f1c9301
Compare
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 think this mostly looks good to me! was only one thing that i noticed.
godot-ffi/src/global.rs
Outdated
|
||
/// The value must be initialized. | ||
pub(super) unsafe fn new_unchecked(mutex_guard: MutexGuard<'a, OnceCell<T>>) -> Self { | ||
debug_assert!(mutex_guard.get().is_some(), "safety precondition violated"); |
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.
"safety precondition violated"
isn't very expressive, better would be "cell not initialized"
or so.
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.
do you mind "safety precondition violated: cell not initialized"
?
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.
That's fine, too!
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.
although actually i think unwrap_unchecked
already has an assertion so maybe i dont need it at all but it cant hurt anyways
godot-ffi/src/global.rs
Outdated
fn deref(&self) -> &Self::Target { | ||
// SAFETY: `self` is `Initialized`. | ||
unsafe { self.mutex_guard.as_initialized().unwrap_unchecked() } | ||
// SAFETY: [`GlobalGuard::new`] being the sole constructor guarantees that the cell is initialized. |
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.
This isn't quite accurate; there is another constructor new_unchecked()
with the same visibility.
But I think it's enough to mention that the invariant of GlobalGuard
is that the cell is initialized. Then ensuring this (and proving its truth via comments) can be done in the constructors.
godot-ffi/src/global.rs
Outdated
} | ||
} | ||
|
||
impl<T> DerefMut for GlobalGuard<'_, T> { | ||
fn deref_mut(&mut self) -> &mut Self::Target { | ||
// SAFETY: `self` is `Initialized`. | ||
unsafe { self.mutex_guard.as_initialized_mut().unwrap_unchecked() } | ||
// SAFETY: [`GlobalGuard::new`] being the sole constructor guarantees that the cell is initialized. |
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.
See above.
5b76701
to
d949495
Compare
The commit message still mentions
but you're now using |
Thanks! |
improves code quality