-
-
Notifications
You must be signed in to change notification settings - Fork 218
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
Global main thread ID #1045
base: master
Are you sure you want to change the base?
Global main thread ID #1045
Conversation
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1045 |
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, good idea!
I'm not sure if I'm doing premature optimization here, but since initialization happens exactly once, I'm not a huge fan of all future code needing to obtain a lock to read the thread ID. This might even be slower than thread::current().id()
, depending on how that is implemented/cached.
In the library we have ManualInitCell<T>
for this purpose. The invariant is that the value is only accessed after initialization, which means it needs to be unsafe
, which is a bit annoying for ergonomics. In Debug mode, misuse would still be caught.
Thoughts? If it's not used in hot paths, we could keep things simple with OnceLock
.
Yeah, I think that is a good idea. I definitely need to check the main thread ID every time a new async task is created, and it's hard to say if that will end up in someone's hot path. It being unsafe is unfortunate, but manageable. |
@Bromeon this probably also fits better into the |
04484f3
to
4d3d3ee
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.
@Bromeon this probably also fits better into the
global
module thanmeta
right?
Not really, that module is for global symbols from Godot itself.
Maybe init/mod.rs
?
This functionality is only needed in godot-core, not in godot-ffi?
godot-core/src/meta/mod.rs
Outdated
/// Get the [`ThreadId`](std::thread::ThreadId) of the main thread. | ||
#[cfg(not(wasm_nothreads))] | ||
pub fn main_thread_id() -> std::thread::ThreadId { | ||
// SAFETY: We initialized the cell during library initialization, before any other code is executed. | ||
let thread_id = unsafe { MAIN_THREAD_ID.get_unchecked() }; | ||
|
||
*thread_id | ||
} |
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.
As much as I'd like it, I'm not sure if we can make this function safe -- the function doesn't have more information than MAIN_THREAD_ID.get_unchecked()
directly, and a caller could technically invoke this before the initialization.
This whole topic deserves some more discussion at some point, because we have the same problem for all the Godot APIs: they too are only valid after the binding has been initialized, but that happens as the very first thing. And yet the accessor functions all remain unsafe.
Maybe there's eventually some API design that allows us to centralize the unsafe
parts related to late initialization, instead of requiring a thousand // SAFETY: binding is initialized
statements scattered through the codebase. But it needs a bit more thought 🤔
cc @lilizoey
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.
a caller could technically invoke this before the initialization.
How would that realistically happen?
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.
a caller could technically invoke this before the initialization.
How would that realistically happen?
just call it from some context outside of running it as a dynamic library. for instance if you call this function from a unit test then the dynamic library initialization code wont have run yet. i dont think that's too unrealistic a scenario for people to do, like they wanna unit test their code and accidentally call something which checks the main thread.
so yeah this cant really be safe, you should probably even add a unit test to check this behavior explicitly.
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.
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 function still can't be safe and public as you've written it currently (though even if private it'd be better if it was unsafe). if you are claiming here that RefCounted::new_gd()
would trigger UB in a unit test just like this function would then that's a bug and should be reported as such. not used as justification for adding more UB to the library.
in practice it may be hard to fix the UB in RefCounted::new_gd()
if that's the case, but still it shouldnt be used to justify this function having UB.
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.
Apologies, that was not the intention.
I was thinking that there is no real opportunity to use anything in a unit test. So I checked the simplest example I could think of, which is creating a new object instance. In a unit test, this panicked in the multithreaded BindingStorage::get_binding_unchecked()
which made sense to me. Therefore, I replicated the behavior in main_thread_id
by calling the safe method ManualInitCell::is_initialized
first and then panicking should the cell not be initialized. If we panic before going into the unsafe
code, we shouldn't have any UB, or not?
Let me know what I missed.
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.
debug_assert
doesn't panic if debug assertions are turned off, so they can't be used to ensure safety. debug_assert
is only useful for sanity checks, and when checking that a safety condition isn't broken it's a soundness bug if it is ever triggered.
like it is possible to run unit tests without debug assertions being turned on for instance by running cargo test --release
. see here for instance. so adding a debug_assert
to check that the value is initialized wont fix the issue that unit tests can call this function before it's initialized.
additionally, you can also just run it as a normal rust binary, for instance by calling the function from fn main()
. there are many ways to call functions in this library without the library initialization code being run.
Hmm, yes, I think it could be used in |
4d3d3ee
to
f0485c1
Compare
In #1043 I need to identify the main thread without accessing the related engine API.
By storing the current thread ID during initialization, we can identify the main thread from that point on. I only found one other use of the main thread ID in
godot-ffi
but having access to the ThreadId of the main-thread could also be useful to user code.