-
-
Notifications
You must be signed in to change notification settings - Fork 221
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
Avoid TLS-destructors causing the dynamic library to not be unloaded when hot reloading #656
Avoid TLS-destructors causing the dynamic library to not be unloaded when hot reloading #656
Conversation
b7e5936
to
1c4372a
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.
Thanks so much! 👍
#[gdextension(enable_hot_reloading = true/false)]
Could this be a trait function in ExtensionLibrary
instead?
The logic that's in default_set_hot_reload
could then become the default implementation.
The KvParser::handle_bool()
could still be useful in the future, maybe you could annotate it with #[allow(dead_code)]
if it's no longer used.
godot-ffi/src/linux.rs
Outdated
pub fn default_set_hot_reload() { | ||
// By default we enable hot reloading for debug builds, as it's likely that the user may want hot reloading in debug builds. | ||
// Release builds however should avoid leaking memory, so we disable hot reloading support by default. | ||
if cfg!(debug_assertions) { | ||
enable_hot_reload() | ||
} else { | ||
disable_hot_reload() | ||
} | ||
} |
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 was thinking to check whether the extension is running in an editor or not, and only enable it if inside editor. This would likely be more robust, because hot-reloading outside editors makes no sense, however it's possible to compile Rust in release mode (for certain optimizations) and still want to hot-reload.
However, it looks like Godot APIs are not available at the time this decision needs to be made.
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.
Any ideas on this? 🙂
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.
maybe by default? im not sure how to do that since we need to make sure to run this code very early on in the initialization and im not sure if we can check that quite that early on
1c4372a
to
4f06a2a
Compare
// Is used in to check that we've been called from the right thread, so must be thread-safe to access. | ||
is_initialized: Cell<bool>, | ||
main_thread_id: Cell<Option<ThreadId>>, |
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 made me think, do we still want to use the ThreadId
in release mode, even if it is not used for any validation?
Pro: uses same semantics as Debug, simpler code
Con: release binaries then don't work by default with reloading (but maybe that's good to point to the override?)
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 binaries can stop working with reloading accidentally if people just add something that creates a TLS destructor that prevents the reloading anyway in this case. So i dont think that's a huge issue. Maybe we can print out a warning somehow when this potentially has happened?
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 warning is a good idea 👍
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 fine if we do this at a later point too (or not at all), no need to block on this 🙂
godot-ffi/src/linux.rs
Outdated
pub fn default_set_hot_reload() { | ||
// By default we enable hot reloading for debug builds, as it's likely that the user may want hot reloading in debug builds. | ||
// Release builds however should avoid leaking memory, so we disable hot reloading support by default. | ||
if cfg!(debug_assertions) { | ||
enable_hot_reload() | ||
} else { | ||
disable_hot_reload() | ||
} | ||
} |
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.
Any ideas on this? 🙂
godot-ffi/src/linux.rs
Outdated
// Avoid TLS-destructors preventing the dynamic library from being closed. | ||
// See: https://fasterthanli.me/articles/so-you-want-to-live-reload-rust#what-can-prevent-dlclose-from-unloading-a-library |
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.
Maybe add something like "Credits to fasterthanlime for discovering the very helpful workaround." or similar, in addition to the link.
godot-ffi/src/linux.rs
Outdated
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
//! Linux-specific configuration. |
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.
Maybe this file could be called linux_reload_workaround.rs
to be more specific. If there is other Linux-specific logic in the future, we can always generalize or add a dedicated linux
module.
godot-ffi/src/linux.rs
Outdated
let name = c"__cxa_thread_atexit_impl".as_ptr(); | ||
std::mem::transmute(libc::dlsym( | ||
libc::RTLD_NEXT, | ||
#[allow(clippy::transmute_ptr_to_ref)] |
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 is needed on on the argument, 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.
It seems like it's not needed at all
godot-ffi/src/linux.rs
Outdated
use std::ffi::c_void; | ||
use std::sync::OnceLock; | ||
|
||
pub type NextFn = unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void); |
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.
Maybe ThreadAtexitFn
?
Add thread-id checking back in
4f06a2a
to
82a14f6
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.
Thanks a lot!
// Is used in to check that we've been called from the right thread, so must be thread-safe to access. | ||
is_initialized: Cell<bool>, | ||
main_thread_id: Cell<Option<ThreadId>>, |
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 fine if we do this at a later point too (or not at all), no need to block on this 🙂
/// Whether to enable hot reloading of this library. Return `None` to use the default behavior. | ||
/// | ||
/// Enabling this will ensure that the library can be hot reloaded. If this is disabled then hot reloading may still work, but there is no | ||
/// guarantee. Enabling this may also lead to memory leaks, so it should not be enabled for builds that are intended to be final builds. | ||
/// | ||
/// By default this is enabled for debug builds and disabled for release builds. | ||
/// | ||
/// Note that this is only checked *once* upon initializing the library. Changing this from `true` to `false` will be picked up as the | ||
/// library is then fully reloaded upon hot-reloading, however changing it from `false` to `true` is almost certainly not going to work | ||
/// unless hot-reloading is already working regardless of this setting. | ||
fn override_hot_reload() -> Option<bool> { | ||
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.
I might still change this later to return bool
and do the release/debug check in the default impl, so that the user-facing method is simpler (there is no reason to override it and return None
).
This seems to work on my end at least for linux.
By default i enable this workaround for debug builds only, as it may leak memory. However the user may override it by setting
#[gdextension(enable_hot_reloading = true/false)]
. This should probably be documented somewhere but im not sure the best place to do so and how to word it.