Skip to content

[3.x] Don't terminate library if other GDNatives are still using it #49467

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

Closed
wants to merge 1 commit into from

Conversation

Waridley
Copy link
Contributor

@Waridley Waridley commented Jun 9, 2021

This PR is being made directly to 3.x because there are apparently so many massive changes to GDNative coming in 4.0 that I have no way to test this or even know if it will be relevant to 4.0.

If a GDNative library is marked as reloadable, GDNative::terminate() checks GDNativeLibrary::loaded_libraries to see if any other GDNative objects are currently using the library before actually closing it, but they weren't actually being added to the list beyond the first one.

Now that NativeScriptLanguage::unregister_script() calls GDNative::terminate() when NativeScripts are left referencing the library (introduced by hot-reloading in 311ca0c), this means that libraries can be terminated out from under GDNative objects just by freeing an object that happens to hold the last NativeScript using a library.

This could happen previously by creating two GDNative objects referencing the same library, and then terminating one of them, but now it's relevant to the more widely-used NativeScripts as well. This is still a relatively rare scenario, using the same library for both a basic GDNative object and creating NativeScripts from it, but this is exactly what the godot-rust tests do to verify that the bindings work both with and without NativeScript functionality.

There is one issue with this fix, which makes it technically a breaking change: without this fix, dropping the last NativeScript terminates the library, and creating a new one would re-initialize it; while with this fix, godot_nativescript_init can end up being called multiple times without terminating the library. Since there is no godot_nativescript_terminate counterpart, libraries have no way of knowing that they can do any NativeScript-related cleanup until godot_gdnative_terminate is called. However, if they were expecting godot_gdnative_terminate to be called before godot_nativescript_init was called again, invariants could be broken and lead to crashes or even UB on the next godot_nativescript_init callback.

The workaround, whether this fix is applied or not, is to mark the library as not-reloadable (luckily this case was fixed by 5228871). However, since the current situation can very easily lead to undefined behavior, a solution needs to be decided on, and the changes in this PR appear to be the original intended behavior.

@Waridley Waridley requested a review from a team as a code owner June 9, 2021 18:09
@Waridley Waridley force-pushed the gdn_terminate_fix branch from 176e194 to cbc79f8 Compare June 9, 2021 18:23
@Calinou Calinou added this to the 3.4 milestone Jun 9, 2021
@akien-mga akien-mga modified the milestones: 3.4, 3.5 Nov 8, 2021
@akien-mga akien-mga force-pushed the 3.x branch 2 times, most recently from 71cb8d3 to c58391c Compare January 6, 2022 22:40
@Waridley Waridley force-pushed the gdn_terminate_fix branch from cbc79f8 to 4452a8d Compare June 28, 2022 21:36
@akien-mga akien-mga modified the milestones: 3.5, 3.x Jul 2, 2022
@akien-mga akien-mga added the cherrypick:3.5 Considered for cherry-picking into a future 3.5.x release label Jul 2, 2022
Waridley added a commit to Waridley/godot-rust that referenced this pull request Sep 19, 2022
Waridley added a commit to Waridley/godot-rust that referenced this pull request Sep 19, 2022
@Waridley
Copy link
Contributor Author

I think this should probably be merged before 3.x is abandoned. The only ways to mitigate the issue are to forfeit reload ability or maybe to keep a dummy NativeScript around to prevent unloading. There are other ways to detect and mitigate the re-calling of godot_nativescript_init

Maybe this will help make it clearer what's going wrong:

now:

  • Library initialized
  • GDNative object created pointing to library
  • NativeScript instance created
    • Allocates another GDNative object pointing to the same library
    • godot_nativescript_init called
  • NativeScript instance dropped
    • 1 GDNative object freed, but 1 remains
    • No more NativeScripts
      • Library unloaded
  • Another NativeScript created
    • Undefined Behavior (SEGFAULT at best)

With this fix:

  • Library initialized
  • GDNative object created pointing to library
  • NativeScript instance created
    • Allocates another GDNative object pointing to the same library
    • godot_nativescript_init called
  • NativeScript instance dropped
    • 1 GDNative object freed, but 1 remains
    • No more NativeScripts
      • Library NOT unloaded due to remaining GDNative object
  • Another NativeScript created
    • Allocates another GDNative object pointing to the same library
    • godot_nativescript_init called again
      • Potentially UB, but preventable by library

@akien-mga
Copy link
Member

CC @godotengine/gdextension @Bromeon

@akien-mga akien-mga modified the milestones: 3.x, 3.6 Dec 12, 2022
@akien-mga akien-mga changed the title Don't terminate library if other GDNatives are still using it [3.x] Don't terminate library if other GDNatives are still using it Aug 18, 2023
@@ -430,6 +433,10 @@ bool GDNative::terminate() {
gdnatives->clear();
// whew this looks scary, but all it does is remove the entry completely
GDNativeLibrary::loaded_libraries.erase(GDNativeLibrary::loaded_libraries.find(library->get_current_library_path()));
} else {
// in case user triggers a call to terminate in the callback below
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// in case user triggers a call to terminate in the callback below
// In case user triggers a call to terminate in the callback below.

Per the comment style.

@lawnjelly lawnjelly modified the milestones: 3.6, 3.7 Sep 11, 2024
@Waridley Waridley closed this Sep 30, 2024
@AThousandShips AThousandShips added archived and removed cherrypick:3.5 Considered for cherry-picking into a future 3.5.x release labels Sep 30, 2024
@AThousandShips AThousandShips removed this from the 3.7 milestone Sep 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: PRs for 3.6 beta 1
Development

Successfully merging this pull request may close these issues.

5 participants