Repository navigation
Description
Godot version
4.8-dev7 (official Windows build)
godot-cpp version or commit hash
694a264 (also reproduced at 5ed72a0; the code involved is unchanged in master at e103d34)
System information
Windows 11, MSVC, x86_64, editor build of the extension
Issue description
After an editor hot reload of a reloadable extension, the editor crashes with an access violation (0xC0000005) at quit. This happens every time, provided the reloaded image has touched any engine singleton.
The cause. godot-cpp releases the instance bindings it put on engine singletons (Time, ResourceUID, IP, Engine, anything reached through get_singleton()) only in ClassDB::deinitialize(GDEXTENSION_INITIALIZATION_CORE) (src/core/class_db.cpp:447-461). That call is reached only when the CORE level's initialisation count returns to zero (src/godot.cpp:212-215, :244-248).
- A first load works. It initialises from CORE, so at quit the count goes from 1 to 0 and the bindings are released.
- A reloaded image does not. The engine initialises it from the extension's minimum level, not from CORE (
core/extension/gdextension_manager.cpp:47-56). At quit its CORE count goes from 0 to -1, so the release is skipped.
The bindings therefore stay on the engine's objects. Godot closes the library in unregister_core_types (core/register_core_types.cpp:440, gdextension.cpp:877-880) and only afterwards deletes ResourceUID, IP and Time (:442, :444, :492). ~Object then calls each binding's free_callback (core/object/object.cpp:2353-2357), which is godot-cpp's _gde_binding_free_callback in an image that is no longer mapped.
The engine clears a reloadable extension's tracked bindings on a reload (gdextension_manager.cpp:185-189), but never at shutdown.
Steps to reproduce
- Build an extension with
entry_symbolminimum levelSCENEandreloadable = true. Make it call something likegodot::Time::get_singleton()->get_ticks_msec()after it is initialised. - Open its project in the editor, then run
GDExtensionManager.reload_extension("res://<its .gdextension>")(or trigger a hot reload by rebuilding). - Quit the editor. It exits with
0xC0000005. Without the reload it exits with 0.
We saw it in 3 of 3 runs at 694a264 and 5 of 5 at 5ed72a0.
Workaround
The extension's terminator calls godot::ClassDB::deinitialize(GDEXTENSION_INITIALIZATION_CORE) itself when the image never received a CORE initialisation. With that, the same runs exit 0 (10 of 10). A second call after a normal release does nothing, because engine_singletons is already empty (src/core/class_db.cpp:451-455).
Suggested fix
Either of these:
- In
deinitialize_level, release the singleton bindings when the library's last initialised level is torn down, whatever that level is. - Have the engine initialise a reloaded library from CORE.
Minimal reproduction project (MRP)
Not attached. The steps above are the whole of it: any reloadable extension that touches an engine singleton reproduces it.
Activity
dsnopek commentedon Nov 3, 2026
Does this PR #1908 help for you?
This could be a duplicate of #1900
dsnopek commentedon Nov 3, 2026
This refers to this code:
I agree that this does seem wrong.
I think the comparison and returning
LOAD_STATUS_NEEDS_RELOADis fine. But callinginitialize_library()starting from the minimum initialization level seems wrong - we shouldn't be skipping earlier levels.UPDATE: Here's a DRAFT PR that I think is correct, but I need to dig into it some more: godotengine/godot#125122