bevy_dynamic_plugin: Don't leak memory#6705
Conversation
alice-i-cecile
left a comment
There was a problem hiding this comment.
I like how much cleaner this is. And avoiding leaking memory could be useful for modding.
However, I'm definitely nervous about the long list of non-local invariants that must be upheld to avoid UB.
That's largely the story of this whole crate though, so my official stance on this PR is "neutral".
This is definitely an issue - as The safety of the running an app with dynamic plugins relies on the safety calling The safety notes on The only more complete solution i can conceive involves obfuscating dynamically-defined types and providing dynamic plugins with a more carefully controlled app scope. I think for now I will change this PR to be leaky by default, with opt-in automatic dropping. |
b6a8662 to
80ab1b3
Compare
|
anyone know what this ci failure is about? |
|
Not your fault: there's duplicate dependencies in tree due to staggered updates. It won't block merging. |
c035dde to
e1ddf8d
Compare
|
Hmm. This is getting a little over my head now. I don't fully understand all the safety implications here. How is this intended to be used exactly? If I'm understanding correctly, this PR adds a mechanism for unloading plugins. I cannot really imagine what that would look like in real user code. |
|
If this was used for modding, unloading plugins would be useful for disabling mods without a game restart. |
|
I understand the benefits of "unloading plugins" as a feature. I meant that I don't understand how this particular API is to be used, and the safety implications of it. |
as it stands i'm not very happy with the api/implementation of this PR. i'm taking another look at the moment to see if i can come up with a better design. |
0daa0e7 to
edc4488
Compare
an example might help: use bevy_dynamic_plugin::{DynamicPluginLibraries, DynamicPluginExt};
use bevy_app::App;
use bevy_ecs::system::ResMut;
const LIB_PATH: &str = "./libmy_dyn_plugin.so";
let mut app = App::new();
unsafe { app.load_plugin(LIB_PATH)
app.add_system(remove_library);
fn remove_library(mut libs: ResMut<DynamicPluginLibraries>) {
// use arbitrary logic to decide whether to unload the library:
if 1 != 2 {
// SAFETY: No resources or components that contain function pointers remain after the app is dropped
// and so code from the library cannot be called after the app is dropped.
// Additionally, we have checked './libmy_dyn_plugin.so' and ensured it does not cause UB when unloaded.
unsafe { libs.mark_for_unloading(LIB_PATH) };
// library is still loaded at this point.
}
}
app.run();
// library is no longer loaded. |
Objective
bevy_dynamic_plugin::dynamically_load_plugincallsstd::mem::forgeton thelibloading::Libraryinstance. An escape hatch should be added to allow users to more easily handle the unloading of dynamic plugins.Solution
Add a
DynamicPluginstruct which can be dropped manually.Add a new resource
DynamicPluginLibrarieswhich allows users to (unsafely) mark libraries for automatic unloading withDynamicPluginLibraries::mark_for_unloading. The library will be dropped when the associatedDynamicPluginis or whenmark_for_unloadingis called, whichever happens last.Changelog
DynamicPlugin.DynamicPluginLibraries.dynamically_load_plugintoDynamicPlugin.bevy_ecsandbevy_utilsdependencies forbevy_dynamic_plugin.Migration Guide
dynamically_load_pluginnow returns aDynamicPlugininstead of a tuple. CallDynamicPlugin::into_raw_partsandArc::try_unwrapon the resulting library if you need the old behavior.