-
-
Notifications
You must be signed in to change notification settings - Fork 589
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
GDExtension Ref<T> object freeing error. #652
Comments
Haven't run into this myself but I have a feeling I soon will. We had similar issues with returning String and Object but not sure how @vnen fixed those. |
I haven't pushed my code yet - cleaning up a little. But I'll have buildable reference plugin for debugging. Need to see what's going wrong with RefCounted |
If returning a ref from a function makes it dangling, then how is such usage even working in core? What I think is happening in Godot, is that on return, a copy of Ref is made, which does implicitely increments refcount for the duration of the return, and that does not seem to happen properly here (although move semantics and some compiler optimizations could reduce this because this increment is fairly redundant at the end of the day). It worked in GDNative because Ref return values were wrapped inside a Variant. Variant had a constructor taking a pointer bindings-side, and that's where the ownership was picked by Godot, in addition ot Ref having a Variant cast godot-cpp/include/core/Ref.hpp Line 103 in 68ce781
|
Attaching the callstack to the location of As I have noted previously - increasing the RefCounted's count by 1 solves this issue without any memory leaks. libgdvideo.dll!godot::Ref<godot::VideoStreamPlaybackExtension>::unref() Line 216 C++
libgdvideo.dll!godot::Ref<godot::VideoStreamPlaybackExtension>::~Ref<godot::VideoStreamPlaybackExtension>() Line 228 C++
libgdvideo.dll!godot::call_with_ptr_args_ret_helper<godot::VideoStreamReference,godot::Ref<godot::VideoStreamPlaybackExtension>>(godot::VideoStreamReference * p_instance, godot::Ref<godot::VideoStreamPlaybackExtension>(godot::VideoStreamReference::*)() p_method, void * const * p_args, void * r_ret, IndexSequence<> __formal) Line 163 C++
libgdvideo.dll!godot::call_with_ptr_args<godot::VideoStreamReference,godot::Ref<godot::VideoStreamPlaybackExtension>>(godot::VideoStreamReference * p_instance, godot::Ref<godot::VideoStreamPlaybackExtension>(godot::VideoStreamReference::*)() p_method, void * const * p_args, void * r_ret) Line 183 C++
libgdvideo.dll!godot::VideoStreamExtension::register_virtuals::__l10::<lambda>(void * p_instance, void * const * p_args, void * p_ret) Line 70 C++
libgdvideo.dll!void <lambda>(void *, void * const *, void *)::<lambda_invoker_cdecl>(void * p_instance, void * const * p_args, void * p_ret) Line 70 C++
godot.windows.tools.64.exe!VideoStreamExtension::_gdvirtual__instance_playback_call(Ref<VideoStreamPlaybackExtension> & r_ret) Line 113 C++
godot.windows.tools.64.exe!VideoStreamExtension::instance_playback() Line 196 C++ |
I think the culprit might be around To be sure if that really happens, we have to step and see what happens in Ref<VideoStreamPlaybackExtension> ref;
if (GDVIRTUAL_CALL(_instance_playback, ref)) { (there is no specialization of PtrToArg for Ref) We also need to make sure that a fix that "doesnt crash" would not create a leak on the other hand. When Godot gets the reference, we need to be sure it's the only one at the end. So it's possible that we would not need to call |
I've run through the calls - template <class T>
struct PtrToArg<Ref<T>> {
_FORCE_INLINE_ static Ref<T> convert(const void *p_ptr) {
return Ref<T>(godot::internal::gdn_interface->object_get_instance_binding((void *)p_ptr, godot::internal::token, &T::___binding_callbacks));
}
typedef Ref<T> EncodeT;
_FORCE_INLINE_ static void encode(Ref<T> p_val, const void *p_ptr) {
*(void **)p_ptr = p_val->_owner;
}
}; Currently - increasing reference on the plugin side doesn't cause a leak (at not reported by ClassDB) as long as the reference is not stored in plugin. I haven't tested with storing but I can test that. I am stuck with end semester evaluations so I won't be able to investigate this until mid December. I will however push my plugin and Godot code to allow easy reproduction in a couple of days. |
Ah, you're right, such specialization exists. I was only looking in godot-cpp/include/godot_cpp/classes/ref.hpp Lines 248 to 250 in 8d25e04
This indeed does not give any ownership. So it's like being given a Ref<T> by reference, but directly assigning the pointer inside this Ref without going through operator= .
If Godot unpacks this as a *(Ref<T>*)p_ptr = p_val; This is, of course, specifically when Godot actually does expect the return value to be However, the current code does if (p_val != nullptr) {
p_val->reference();
*(void**)p_ptr = p_val->_owner;
} else {
*(void**)p_ptr = nullptr;
} This code also assumes |
I think Godot is providing a pointer to the adress where the return value begins. In this case, a There cannot be two separate reference counts because the reference count is stored inside the Godot object. So either side could increment or decrement, it should remain consistent, as long as each side does it according to the same rules.
Is this after trying with the code I suggested? Or the original code?
Not exactly sure what's up there, but current encode looks wrong. So far all I've done is read the code and deduced what should be done from what I know, I need to setup Godot 4 and the associated CppBindings to actually give it a try. |
Made a mistake in debugging. Attaching the Godot stack trace for the crash at the bottom as (A).
It was with Original code. Should be fixed. Will make a PR once tested satisfactorily. Adding links to my plugin and godot branch for reproduction. (Let me know any mistakes I might have made - easier to fix while everything is WIP) Note that the fix has already been applied via submodules (The A:
|
Writing a conclusion as to why this is logically correct and not a Hack. Original Code: godot-cpp/include/godot_cpp/classes/ref.hpp Lines 248 to 250 in 8d25e04
This directly does the ownership transfer without incrementing
Thus at n=1, memdelete is called and the if (p_val != nullptr) {
p_val->reference();
*(void**)p_ptr = p_val->_owner;
} else {
*(void**)p_ptr = nullptr;
} We increment the count manually (1 extra count before doing the pointer assignment thereby increasing the number of references by 1)
Thus the invariant count is correct at the end. The fix is logically correct. |
These are likely related to refcounting issues. The change in the SConstruct script fixes a crash that occured somewhere in `bind_get_argument_type` when registering classes and binding methods. Commenting out the `delete`s in destructors fixed a `double free or corruption` crash that occured after the fix described above. Likely related to godotengine/godot-cpp#652, but neither godotengine/godot-cpp#660 or godotengine/godot-cpp#662 seemed to help...
These are likely related to refcounting issues. The change in the SConstruct script fixes a crash that occured somewhere in `bind_get_argument_type` when registering classes and binding methods. Commenting out the `delete`s in destructors fixed a `double free or corruption` crash that occured after the fix described above. Likely related to godotengine/godot-cpp#652, but neither godotengine/godot-cpp#660 or godotengine/godot-cpp#662 seemed to help...
Adds VideoStream and relevant resource loaders to migrate external GDNative plugins to GDExtension. Theora is modified to use new base class and register with VideoDecoderServer. There are known bugs in godot-cpp w.r.t. reference counting in Ref (godotengine/godot-cpp#652) However, from godot's side this should work just fine once that is fixed.
Adds VideoStream and relevant resource loaders to migrate external GDNative plugins to GDExtension. Adds a VideoStreamLoader as a specialization of ResourceFormatLoader as ClassDB::is_parent_class is inaccessible from GDExtension currently. Using Object* instead of Ref<T> in order to avoid the refcount bug (godotengine/godot-cpp#652) Also another bug is in ResourceLoader in use on the extension side that requires fixing.
Fixed via #958. |
Adds VideoStream and relevant resource loaders to migrate external GDNative plugins to GDExtension. Adds a VideoStreamLoader as a specialization of ResourceFormatLoader as ClassDB::is_parent_class is inaccessible from GDExtension currently. Using Object* instead of Ref<T> in order to avoid the refcount bug (godotengine/godot-cpp#652) Also another bug is in ResourceLoader in use on the extension side that requires fixing.
Adds VideoStream and relevant resource loaders to migrate external GDNative plugins to GDExtension. Adds a VideoStreamLoader as a specialization of ResourceFormatLoader as ClassDB::is_parent_class is inaccessible from GDExtension currently. Using Object* instead of Ref<T> in order to avoid the refcount bug (godotengine/godot-cpp#652) Also another bug is in ResourceLoader in use on the extension side that requires fixing.
Background Info:
Working on adding GDExtension support for Video playback.
One part of the
VideoStream
isRef<VideoStreamPlayback> VideoStream::instance_playback()
For the
VideoStreamExtension
class this is implemented asOn the GDExtension plugin side:
The Issue:
The above implementation crashes with:
Stepping through in the debugger shows the
VideoStreamPlaybackReference::_cleanup
(Destructor) is called on return fromAs the Ref counts to 0.
If one stores the Ref in the plugin: Godot and the Plugin both has separate 'ownership' of the object. As such after cleanup on one end - the other Ref dangles.
Temporary Solution:
Adding this increments the refcount and prevents the deletion after encode.
The Ref quietly deletes and reduces refcount to 1 which is copied and owned by Godot
Works for this particular case since it's an ownership transfer. But it will not work in Shared ownership case as RefCounted object seems to not be shared across the boundary.
Related: #417
The text was updated successfully, but these errors were encountered: