Fix/workaround for issue #21667

When a Reference managed instance is garbage collected and its finalizer is called, it could happen that the native instance is referenced once again before the finalizer can unreference and memdelete it. The workaround is to create a new managed instance when this happens (at least for now).
This commit is contained in:
Ignacio Etcheverry 2018-09-12 02:41:54 +02:00
parent 61426464ea
commit e558e1ec09
15 changed files with 512 additions and 196 deletions

View file

@ -138,7 +138,7 @@ void CSharpLanguage::finish() {
#endif
// Release gchandle bindings before finalizing mono runtime
gchandle_bindings.clear();
script_bindings.clear();
if (gdmono) {
memdelete(gdmono);
@ -551,22 +551,22 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec
void CSharpLanguage::frame() {
const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoUtils::mono_cache.task_scheduler_handle;
if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != NULL) {
const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoUtils::mono_cache.task_scheduler_handle;
if (task_scheduler_handle.is_valid()) {
MonoObject *task_scheduler = task_scheduler_handle->get_target();
if (task_scheduler_handle.is_valid()) {
MonoObject *task_scheduler = task_scheduler_handle->get_target();
if (task_scheduler) {
GDMonoUtils::GodotTaskScheduler_Activate thunk = CACHED_METHOD_THUNK(GodotTaskScheduler, Activate);
if (task_scheduler) {
GDMonoUtils::GodotTaskScheduler_Activate thunk = CACHED_METHOD_THUNK(GodotTaskScheduler, Activate);
ERR_FAIL_NULL(thunk);
MonoException *exc = NULL;
thunk(task_scheduler, (MonoObject **)&exc);
MonoException *exc = NULL;
thunk(task_scheduler, (MonoObject **)&exc);
if (exc) {
GDMonoUtils::debug_unhandled_exception(exc);
_UNREACHABLE_();
if (exc) {
GDMonoUtils::debug_unhandled_exception(exc);
_UNREACHABLE_();
}
}
}
}
@ -892,6 +892,48 @@ void CSharpLanguage::set_language_index(int p_idx) {
lang_idx = p_idx;
}
void CSharpLanguage::release_script_gchandle(Ref<MonoGCHandle> &p_gchandle) {
if (!p_gchandle->is_released()) { // Do not locking unnecessarily
#ifndef NO_THREADS
get_singleton()->script_gchandle_release_lock->lock();
#endif
p_gchandle->release();
#ifndef NO_THREADS
get_singleton()->script_gchandle_release_lock->unlock();
#endif
}
}
void CSharpLanguage::release_script_gchandle(MonoObject *p_pinned_expected_obj, Ref<MonoGCHandle> &p_gchandle) {
uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_pinned_expected_obj); // we might lock after this, so pin it
if (!p_gchandle->is_released()) { // Do not locking unnecessarily
#ifndef NO_THREADS
get_singleton()->script_gchandle_release_lock->lock();
#endif
MonoObject *target = p_gchandle->get_target();
// We release the gchandle if it points to the MonoObject* we expect (otherwise it was
// already released and could have been replaced) or if we can't get its target MonoObject*
// (which doesn't necessarily mean it was released, and we want it released in order to
// avoid locking other threads unnecessarily).
if (target == p_pinned_expected_obj || target == NULL) {
p_gchandle->release();
}
#ifndef NO_THREADS
get_singleton()->script_gchandle_release_lock->unlock();
#endif
}
MonoGCHandle::free_handle(pinned_gchandle);
}
CSharpLanguage::CSharpLanguage() {
ERR_FAIL_COND(singleton);
@ -904,9 +946,11 @@ CSharpLanguage::CSharpLanguage() {
#ifdef NO_THREADS
lock = NULL;
gchandle_bind_lock = NULL;
script_gchandle_release_lock = NULL;
#else
lock = Mutex::create();
script_bind_lock = Mutex::create();
script_gchandle_release_lock = Mutex::create();
#endif
lang_idx = -1;
@ -926,6 +970,11 @@ CSharpLanguage::~CSharpLanguage() {
script_bind_lock = NULL;
}
if (script_gchandle_release_lock) {
memdelete(script_gchandle_release_lock);
script_gchandle_release_lock = NULL;
}
singleton = NULL;
}
@ -954,6 +1003,22 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
ERR_FAIL_NULL_V(mono_object, NULL);
CSharpScriptBinding script_binding;
script_binding.type_name = type_name;
script_binding.wrapper_class = type_class; // cache
script_binding.gchandle = MonoGCHandle::create_strong(mono_object);
#ifndef NO_THREADS
script_bind_lock->lock();
#endif
void *data = (void *)script_bindings.insert(p_object, script_binding);
#ifndef NO_THREADS
script_bind_lock->unlock();
#endif
// Tie managed to unmanaged
Reference *ref = Object::cast_to<Reference>(p_object);
@ -961,23 +1026,11 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
// Unsafe refcount increment. The managed instance also counts as a reference.
// This way if the unmanaged world has no references to our owner
// but the managed instance is alive, the refcount will be 1 instead of 0.
// See: _GodotSharp::_dispose_object(Object *p_object)
// See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
ref->reference();
}
Ref<MonoGCHandle> gchandle = MonoGCHandle::create_strong(mono_object);
#ifndef NO_THREADS
script_bind_lock->lock();
#endif
void *data = (void *)gchandle_bindings.insert(p_object, gchandle);
#ifndef NO_THREADS
script_bind_lock->unlock();
#endif
return data;
}
@ -985,7 +1038,7 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
if (GDMono::get_singleton() == NULL) {
#ifdef DEBUG_ENABLED
CRASH_COND(!gchandle_bindings.empty());
CRASH_COND(!script_bindings.empty());
#endif
// Mono runtime finalized, all the gchandle bindings were already released
return;
@ -998,15 +1051,15 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
script_bind_lock->lock();
#endif
Map<Object *, Ref<MonoGCHandle> >::Element *data = (Map<Object *, Ref<MonoGCHandle> >::Element *)p_data;
Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data;
// Set the native instance field to IntPtr.Zero, if not yet garbage collected
MonoObject *mono_object = data->value()->get_target();
MonoObject *mono_object = data->value().gchandle->get_target();
if (mono_object) {
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
}
gchandle_bindings.erase(data);
script_bindings.erase(data);
#ifndef NO_THREADS
script_bind_lock->unlock();
@ -1024,7 +1077,7 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
void *data = p_object->get_script_instance_binding(get_language_index());
if (!data)
return;
Ref<MonoGCHandle> &gchandle = ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->get();
Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle;
if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
// The reference count was increased after the managed side was the only one referencing our owner.
@ -1036,7 +1089,7 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
return; // Called after the managed side was collected, so nothing to do here
// Release the current weak handle and replace it with a strong handle.
uint32_t strong_gchandle = MonoGCHandle::make_strong_handle(target);
uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(target);
gchandle->release();
gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE);
}
@ -1055,7 +1108,7 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
void *data = p_object->get_script_instance_binding(get_language_index());
if (!data)
return refcount == 0;
Ref<MonoGCHandle> &gchandle = ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->get();
Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle;
if (refcount == 1 && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
// If owner owner is no longer referenced by the unmanaged side,
@ -1066,7 +1119,7 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
return refcount == 0; // Called after the managed side was collected, so nothing to do here
// Release the current strong handle and replace it with a weak handle.
uint32_t weak_gchandle = MonoGCHandle::make_weak_handle(target);
uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(target);
gchandle->release();
gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE);
@ -1096,9 +1149,8 @@ CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpS
}
MonoObject *CSharpInstance::get_mono_object() const {
#ifdef DEBUG_ENABLED
CRASH_COND(gchandle.is_null());
#endif
ERR_FAIL_COND_V(gchandle.is_null(), NULL);
return gchandle->get_target();
}
@ -1326,10 +1378,12 @@ void CSharpInstance::call_multilevel_reversed(const StringName &p_method, const
call_multilevel(p_method, p_args, p_argcount);
}
void CSharpInstance::_reference_owner_unsafe() {
bool CSharpInstance::_reference_owner_unsafe() {
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
CRASH_COND(owner == NULL);
CRASH_COND(unsafe_referenced); // already referenced
#endif
// Unsafe refcount increment. The managed instance also counts as a reference.
@ -1338,36 +1392,107 @@ void CSharpInstance::_reference_owner_unsafe() {
// See: _unreference_owner_unsafe()
// May not me referenced yet, so we must use init_ref() instead of reference()
Object::cast_to<Reference>(owner)->init_ref();
bool success = Object::cast_to<Reference>(owner)->init_ref();
unsafe_referenced = success;
return success;
}
void CSharpInstance::_unreference_owner_unsafe() {
bool CSharpInstance::_unreference_owner_unsafe() {
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
CRASH_COND(owner == NULL);
#endif
if (!unsafe_referenced)
return false; // Already unreferenced
// Called from CSharpInstance::mono_object_disposed() or ~CSharpInstance()
// Unsafe refcount decrement. The managed instance also counts as a reference.
// See: _reference_owner_unsafe()
if (Object::cast_to<Reference>(owner)->unreference()) {
bool die = static_cast<Reference *>(owner)->unreference();
if (die) {
memdelete(owner);
owner = NULL;
}
return die;
}
void CSharpInstance::mono_object_disposed() {
MonoObject *CSharpInstance::_internal_new_managed() {
#ifdef DEBUG_ENABLED
CRASH_COND(!gchandle.is_valid());
#endif
CSharpLanguage::get_singleton()->release_script_gchandle(gchandle);
ERR_FAIL_NULL_V(owner, NULL);
ERR_FAIL_COND_V(script.is_null(), NULL);
if (base_ref)
_unreference_owner_unsafe();
_reference_owner_unsafe();
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script->script_class->get_mono_ptr());
if (!mono_object) {
script = Ref<CSharpScript>();
owner->set_script_instance(NULL);
ERR_EXPLAIN("Failed to allocate memory for the object");
ERR_FAIL_V(NULL);
}
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner);
// Construct
GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
ctor->invoke_raw(mono_object, NULL);
// Tie managed to unmanaged
gchandle = MonoGCHandle::create_strong(mono_object);
return mono_object;
}
bool CSharpInstance::mono_object_disposed(MonoObject *p_obj) {
#ifdef DEBUG_ENABLED
CRASH_COND(base_ref == true);
CRASH_COND(gchandle.is_null());
#endif
CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle);
}
bool CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_owner_deleted) {
#ifdef DEBUG_ENABLED
CRASH_COND(base_ref == false);
CRASH_COND(gchandle.is_null());
#endif
if (_unreference_owner_unsafe()) {
r_owner_deleted = true;
} else {
r_owner_deleted = false;
CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle);
if (p_is_finalizer) {
// If the native instance is still alive, then it was
// referenced from another thread before the finalizer could
// unreference it and delete it, so we want to keep it.
// GC.ReRegisterForFinalize(this) is not safe because the objects
// referenced by this could have already been collected.
// Instead we will create a new managed instance here.
_internal_new_managed();
}
}
}
void CSharpInstance::refcount_incremented() {
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
CRASH_COND(owner == NULL);
#endif
Reference *ref_owner = Object::cast_to<Reference>(owner);
@ -1378,7 +1503,7 @@ void CSharpInstance::refcount_incremented() {
// so the owner must hold the managed side alive again to avoid it from being GCed.
// Release the current weak handle and replace it with a strong handle.
uint32_t strong_gchandle = MonoGCHandle::make_strong_handle(gchandle->get_target());
uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(gchandle->get_target());
gchandle->release();
gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE);
}
@ -1388,6 +1513,7 @@ bool CSharpInstance::refcount_decremented() {
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
CRASH_COND(owner == NULL);
#endif
Reference *ref_owner = Object::cast_to<Reference>(owner);
@ -1399,7 +1525,7 @@ bool CSharpInstance::refcount_decremented() {
// the managed instance takes responsibility of deleting the owner when GCed.
// Release the current strong handle and replace it with a weak handle.
uint32_t weak_gchandle = MonoGCHandle::make_weak_handle(gchandle->get_target());
uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(gchandle->get_target());
gchandle->release();
gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE);
@ -1470,25 +1596,64 @@ MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variab
void CSharpInstance::notification(int p_notification) {
MonoObject *mono_object = get_mono_object();
if (p_notification == Object::NOTIFICATION_PREDELETE) {
if (mono_object != NULL) { // otherwise it was collected, and the finalizer already called NOTIFICATION_PREDELETE
call_notification_no_check(mono_object, p_notification);
// Set the native instance field to IntPtr.Zero
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
// When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose().
// It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed
// to be sent at least once, which happens right before the call to the destructor.
if (base_ref) {
// It's not safe to proceed if the owner derives Reference and the refcount reached 0.
// At this point, Dispose() was already called (manually or from the finalizer) so
// that's not a problem. The refcount wouldn't have reached 0 otherwise, since the
// managed side references it and Dispose() needs to be called to release it.
// However, this means C# Reference scripts can't receive NOTIFICATION_PREDELETE, but
// this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784
return;
}
_call_notification(p_notification);
MonoObject *mono_object = get_mono_object();
ERR_FAIL_NULL(mono_object);
GDMonoUtils::GodotObject_Dispose thunk = CACHED_METHOD_THUNK(GodotObject, Dispose);
MonoException *exc = NULL;
thunk(mono_object, (MonoObject **)&exc);
if (exc) {
GDMonoUtils::set_pending_exception(exc);
}
return;
}
call_notification_no_check(mono_object, p_notification);
_call_notification(p_notification);
}
void CSharpInstance::call_notification_no_check(MonoObject *p_mono_object, int p_notification) {
Variant value = p_notification;
const Variant *args[1] = { &value };
void CSharpInstance::_call_notification(int p_notification) {
_call_multilevel(p_mono_object, CACHED_STRING_NAME(_notification), args, 1);
MonoObject *mono_object = get_mono_object();
ERR_FAIL_NULL(mono_object);
// Custom version of _call_multilevel, optimized for _notification
uint32_t arg = p_notification;
void *args[1] = { &arg };
StringName method_name = CACHED_STRING_NAME(_notification);
GDMonoClass *top = script->script_class;
while (top && top != script->native) {
GDMonoMethod *method = top->get_method(method_name, 1);
if (method) {
method->invoke_raw(mono_object, args);
return;
}
top = top->get_parent_class();
}
}
Ref<Script> CSharpInstance::get_script() const {
@ -1501,11 +1666,11 @@ ScriptLanguage *CSharpInstance::get_language() {
return CSharpLanguage::get_singleton();
}
CSharpInstance::CSharpInstance() {
owner = NULL;
base_ref = false;
ref_dying = false;
CSharpInstance::CSharpInstance() :
owner(NULL),
base_ref(false),
ref_dying(false),
unsafe_referenced(false) {
}
CSharpInstance::~CSharpInstance() {
@ -1514,10 +1679,7 @@ CSharpInstance::~CSharpInstance() {
gchandle->release(); // Make sure it's released
}
if (base_ref && !ref_dying) { // it may be called from the owner's destructor
#ifdef DEBUG_ENABLED
CRASH_COND(!owner); // dunno, just in case
#endif
if (base_ref && !ref_dying && owner) { // it may be called from the owner's destructor
_unreference_owner_unsafe();
}
@ -1586,29 +1748,30 @@ bool CSharpScript::_update_exports() {
exported_members_cache.clear();
exported_members_defval_cache.clear();
// We are creating a temporary new instance of the class here to get the default value
// TODO Workaround. Should be replaced with IL opcodes analysis
// Here we create a temporary managed instance of the class to get the initial values
MonoObject *tmp_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr());
if (tmp_object) {
CACHED_FIELD(GodotObject, ptr)->set_value_raw(tmp_object, tmp_object); // FIXME WTF is this workaround
GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
MonoException *exc = NULL;
ctor->invoke(tmp_object, NULL, &exc);
if (exc) {
ERR_PRINT("Exception thrown from constructor of temporary MonoObject:");
GDMonoUtils::debug_print_unhandled_exception(exc);
tmp_object = NULL;
ERR_FAIL_V(false);
}
} else {
if (!tmp_object) {
ERR_PRINT("Failed to create temporary MonoObject");
return false;
}
uint32_t tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed)
GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
MonoException *ctor_exc = NULL;
ctor->invoke(tmp_object, NULL, &ctor_exc);
if (ctor_exc) {
MonoGCHandle::free_handle(tmp_pinned_gchandle);
tmp_object = NULL;
ERR_PRINT("Exception thrown from constructor of temporary MonoObject:");
GDMonoUtils::debug_print_unhandled_exception(ctor_exc);
return false;
}
GDMonoClass *top = script_class;
while (top && top != native) {
@ -1666,6 +1829,21 @@ bool CSharpScript::_update_exports() {
top = top->get_parent_class();
}
// Dispose the temporary managed instance
GDMonoUtils::GodotObject_Dispose thunk = CACHED_METHOD_THUNK(GodotObject, Dispose);
MonoException *exc = NULL;
thunk(tmp_object, (MonoObject **)&exc);
if (exc) {
ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:");
GDMonoUtils::debug_print_unhandled_exception(exc);
}
MonoGCHandle::free_handle(tmp_pinned_gchandle);
tmp_object = NULL;
}
if (placeholders.size()) {
@ -2012,6 +2190,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
ERR_FAIL_V(NULL);
}
uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(mono_object); // we might lock after this, so pin it
#ifndef NO_THREADS
CSharpLanguage::singleton->lock->lock();
#endif
@ -2033,6 +2213,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
/* STEP 3, PARTY */
MonoGCHandle::free_handle(pinned_gchandle);
//@TODO make thread safe
return instance;
}

View file

@ -48,6 +48,8 @@ class CSharpLanguage;
#ifdef NO_SAFE_CAST
template <typename TScriptInstance, typename TScriptLanguage>
TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
if (!p_inst)
return NULL;
return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : NULL;
}
#else
@ -177,14 +179,19 @@ class CSharpInstance : public ScriptInstance {
friend class CSharpScript;
friend class CSharpLanguage;
Object *owner;
Ref<CSharpScript> script;
Ref<MonoGCHandle> gchandle;
bool base_ref;
bool ref_dying;
bool unsafe_referenced;
void _reference_owner_unsafe();
void _unreference_owner_unsafe();
Ref<CSharpScript> script;
Ref<MonoGCHandle> gchandle;
bool _reference_owner_unsafe();
bool _unreference_owner_unsafe();
MonoObject *_internal_new_managed();
// Do not use unless you know what you are doing
friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
@ -208,7 +215,8 @@ public:
virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount);
virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount);
void mono_object_disposed();
bool mono_object_disposed(MonoObject *p_obj);
bool mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_owner_deleted);
virtual void refcount_incremented();
virtual bool refcount_decremented();
@ -217,7 +225,7 @@ public:
virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
virtual void notification(int p_notification);
void call_notification_no_check(MonoObject *p_mono_object, int p_notification);
void _call_notification(int p_notification);
virtual Ref<Script> get_script() const;
@ -227,6 +235,12 @@ public:
~CSharpInstance();
};
struct CSharpScriptBinding {
StringName type_name;
GDMonoClass *wrapper_class;
Ref<MonoGCHandle> gchandle;
};
class CSharpLanguage : public ScriptLanguage {
friend class CSharpScript;
@ -241,10 +255,11 @@ class CSharpLanguage : public ScriptLanguage {
Mutex *lock;
Mutex *script_bind_lock;
Mutex *script_gchandle_release_lock;
Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload;
Map<Object *, Ref<MonoGCHandle> > gchandle_bindings;
Map<Object *, CSharpScriptBinding> script_bindings;
struct StringNameCache {
@ -270,6 +285,9 @@ public:
_FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; }
static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle);
static void release_script_gchandle(MonoObject *p_pinned_expected_obj, Ref<MonoGCHandle> &p_gchandle);
bool debug_break(const String &p_error, bool p_allow_continue = true);
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);

View file

@ -53,9 +53,9 @@ void MonoBottomPanel::_update_build_tabs_list() {
build_tabs_list->add_item(item_name, tab->get_icon_texture());
String item_tooltip = String("Solution: ") + tab->build_info.solution;
item_tooltip += String("\nConfiguration: ") + tab->build_info.configuration;
item_tooltip += String("\nStatus: ");
String item_tooltip = "Solution: " + tab->build_info.solution;
item_tooltip += "\nConfiguration: " + tab->build_info.configuration;
item_tooltip += "\nStatus: ";
if (tab->build_exited) {
item_tooltip += tab->build_result == MonoBuildTab::RESULT_SUCCESS ? "Succeeded" : "Errored";

View file

@ -35,21 +35,78 @@
#include "core/reference.h"
#include "core/string_db.h"
#include "../csharp_script.h"
#include "../mono_gd/gd_mono_internals.h"
#include "../mono_gd/gd_mono_utils.h"
#include "../signal_awaiter_utils.h"
Object *godot_icall_Object_Ctor(MonoObject *obj) {
Object *godot_icall_Object_Ctor(MonoObject *p_obj) {
Object *instance = memnew(Object);
GDMonoInternals::tie_managed_to_unmanaged(obj, instance);
GDMonoInternals::tie_managed_to_unmanaged(p_obj, instance);
return instance;
}
void godot_icall_Object_Dtor(MonoObject *obj, Object *ptr) {
void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) {
#ifdef DEBUG_ENABLED
CRASH_COND(ptr == NULL);
CRASH_COND(p_ptr == NULL);
#endif
_GodotSharp::get_singleton()->queue_dispose(obj, ptr);
if (p_ptr->get_script_instance()) {
CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance());
if (cs_instance) {
cs_instance->mono_object_disposed(p_obj);
p_ptr->set_script_instance(NULL);
return;
}
}
void *data = p_ptr->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
if (data) {
Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle;
if (gchandle.is_valid()) {
CSharpLanguage::release_script_gchandle(p_obj, gchandle);
}
}
}
void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, bool p_is_finalizer) {
#ifdef DEBUG_ENABLED
CRASH_COND(p_ptr == NULL);
// This is only called with Reference derived classes
CRASH_COND(!Object::cast_to<Reference>(p_ptr));
#endif
Reference *ref = static_cast<Reference *>(p_ptr);
if (ref->get_script_instance()) {
CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(ref->get_script_instance());
if (cs_instance) {
bool r_owner_deleted;
cs_instance->mono_object_disposed_baseref(p_obj, p_is_finalizer, r_owner_deleted);
if (!r_owner_deleted && !p_is_finalizer) {
// If the native instance is still alive and Dispose() was called
// (instead of the finalizer), then we remove the script instance.
ref->set_script_instance(NULL);
}
return;
}
}
// Unsafe refcount decrement. The managed instance also counts as a reference.
// See: CSharpLanguage::alloc_instance_binding_data(Object *p_object)
if (ref->unreference()) {
memdelete(ref);
} else {
void *data = ref->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
if (data) {
Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle;
if (gchandle.is_valid()) {
CSharpLanguage::release_script_gchandle(p_obj, gchandle);
}
}
}
}
MethodBind *godot_icall_Object_ClassDB_get_method(MonoString *p_type, MonoString *p_method) {
@ -87,7 +144,8 @@ Error godot_icall_SignalAwaiter_connect(Object *p_source, MonoString *p_signal,
void godot_register_object_icalls() {
mono_add_internal_call("Godot.Object::godot_icall_Object_Ctor", (void *)godot_icall_Object_Ctor);
mono_add_internal_call("Godot.Object::godot_icall_Object_Dtor", (void *)godot_icall_Object_Dtor);
mono_add_internal_call("Godot.Object::godot_icall_Object_Disposed", (void *)godot_icall_Object_Disposed);
mono_add_internal_call("Godot.Object::godot_icall_Reference_Disposed", (void *)godot_icall_Reference_Disposed);
mono_add_internal_call("Godot.Object::godot_icall_Object_ClassDB_get_method", (void *)godot_icall_Object_ClassDB_get_method);
mono_add_internal_call("Godot.Object::godot_icall_Object_weakref", (void *)godot_icall_Object_weakref);
mono_add_internal_call("Godot.SignalAwaiter::godot_icall_SignalAwaiter_connect", (void *)godot_icall_SignalAwaiter_connect);

View file

@ -38,9 +38,11 @@
#include "../mono_gd/gd_mono_marshal.h"
Object *godot_icall_Object_Ctor(MonoObject *obj);
Object *godot_icall_Object_Ctor(MonoObject *p_obj);
void godot_icall_Object_Dtor(MonoObject *obj, Object *ptr);
void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr);
void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, bool p_is_finalizer);
MethodBind *godot_icall_Object_ClassDB_get_method(MonoString *p_type, MonoString *p_method);

View file

@ -54,11 +54,6 @@ namespace Godot.Collections
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;

View file

@ -58,11 +58,6 @@ namespace Godot.Collections
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;

View file

@ -54,7 +54,11 @@ namespace Godot
if (memoryOwn)
{
memoryOwn = false;
godot_icall_Object_Dtor(this, ptr);
godot_icall_Reference_Disposed(this, ptr, !disposing);
}
else
{
godot_icall_Object_Disposed(this, ptr);
}
this.ptr = IntPtr.Zero;
@ -72,7 +76,10 @@ namespace Godot
internal extern static IntPtr godot_icall_Object_Ctor(Object obj);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_Object_Dtor(object obj, IntPtr ptr);
internal extern static void godot_icall_Object_Disposed(Object obj, IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_Reference_Disposed(Object obj, IntPtr ptr, bool isFinalizer);
// Used by the generated API
[MethodImpl(MethodImplOptions.InternalCall)]

View file

@ -32,24 +32,34 @@
#include "mono_gd/gd_mono.h"
uint32_t MonoGCHandle::make_strong_handle(MonoObject *p_object) {
uint32_t MonoGCHandle::new_strong_handle(MonoObject *p_object) {
return mono_gchandle_new(p_object, /* pinned: */ false);
}
uint32_t MonoGCHandle::make_weak_handle(MonoObject *p_object) {
uint32_t MonoGCHandle::new_strong_handle_pinned(MonoObject *p_object) {
return mono_gchandle_new(p_object, /* pinned: */ true);
}
uint32_t MonoGCHandle::new_weak_handle(MonoObject *p_object) {
return mono_gchandle_new_weakref(p_object, /* track_resurrection: */ false);
}
void MonoGCHandle::free_handle(uint32_t p_gchandle) {
mono_gchandle_free(p_gchandle);
}
Ref<MonoGCHandle> MonoGCHandle::create_strong(MonoObject *p_object) {
return memnew(MonoGCHandle(make_strong_handle(p_object), STRONG_HANDLE));
return memnew(MonoGCHandle(new_strong_handle(p_object), STRONG_HANDLE));
}
Ref<MonoGCHandle> MonoGCHandle::create_weak(MonoObject *p_object) {
return memnew(MonoGCHandle(make_weak_handle(p_object), WEAK_HANDLE));
return memnew(MonoGCHandle(new_weak_handle(p_object), WEAK_HANDLE));
}
void MonoGCHandle::release() {
@ -59,7 +69,7 @@ void MonoGCHandle::release() {
#endif
if (!released && GDMono::get_singleton()->is_runtime_initialized()) {
mono_gchandle_free(handle);
free_handle(handle);
released = true;
}
}

View file

@ -49,12 +49,15 @@ public:
WEAK_HANDLE
};
static uint32_t make_strong_handle(MonoObject *p_object);
static uint32_t make_weak_handle(MonoObject *p_object);
static uint32_t new_strong_handle(MonoObject *p_object);
static uint32_t new_strong_handle_pinned(MonoObject *p_object);
static uint32_t new_weak_handle(MonoObject *p_object);
static void free_handle(uint32_t p_gchandle);
static Ref<MonoGCHandle> create_strong(MonoObject *p_object);
static Ref<MonoGCHandle> create_weak(MonoObject *p_object);
_FORCE_INLINE_ bool is_released() { return released; }
_FORCE_INLINE_ bool is_weak() { return weak; }
_FORCE_INLINE_ MonoObject *get_target() const { return released ? NULL : mono_gchandle_get_target(handle); }

View file

@ -639,9 +639,7 @@ Error GDMono::_unload_scripts_domain() {
mono_gc_collect(mono_gc_max_generation());
finalizing_scripts_domain = true;
mono_domain_finalize(scripts_domain, 2000);
finalizing_scripts_domain = false;
mono_gc_collect(mono_gc_max_generation());
@ -837,7 +835,6 @@ GDMono::GDMono() {
gdmono_log = memnew(GDMonoLog);
runtime_initialized = false;
finalizing_scripts_domain = false;
root_domain = NULL;
scripts_domain = NULL;
@ -866,7 +863,7 @@ GDMono::GDMono() {
GDMono::~GDMono() {
if (runtime_initialized) {
if (is_runtime_initialized()) {
if (scripts_domain) {
@ -891,8 +888,9 @@ GDMono::~GDMono() {
print_verbose("Mono: Runtime cleanup...");
runtime_initialized = false;
mono_jit_cleanup(root_domain);
runtime_initialized = false;
}
if (gdmono_log)
@ -903,33 +901,12 @@ GDMono::~GDMono() {
_GodotSharp *_GodotSharp::singleton = NULL;
void _GodotSharp::_dispose_object(Object *p_object) {
if (p_object->get_script_instance()) {
CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_object->get_script_instance());
if (cs_instance) {
cs_instance->mono_object_disposed();
return;
}
}
// Unsafe refcount decrement. The managed instance also counts as a reference.
// See: CSharpLanguage::alloc_instance_binding_data(Object *p_object)
if (Object::cast_to<Reference>(p_object)->unreference()) {
memdelete(p_object);
}
}
void _GodotSharp::_dispose_callback() {
#ifndef NO_THREADS
queue_mutex->lock();
#endif
for (List<Object *>::Element *E = obj_delete_queue.front(); E; E = E->next()) {
_dispose_object(E->get());
}
for (List<NodePath *>::Element *E = np_delete_queue.front(); E; E = E->next()) {
memdelete(E->get());
}
@ -938,7 +915,6 @@ void _GodotSharp::_dispose_callback() {
memdelete(E->get());
}
obj_delete_queue.clear();
np_delete_queue.clear();
rid_delete_queue.clear();
queue_empty = true;
@ -958,52 +934,69 @@ void _GodotSharp::detach_thread() {
GDMonoUtils::detach_current_thread();
}
bool _GodotSharp::is_finalizing_domain() {
int32_t _GodotSharp::get_domain_id() {
return GDMono::get_singleton()->is_finalizing_scripts_domain();
MonoDomain *domain = mono_domain_get();
CRASH_COND(!domain); // User must check if runtime is initialized before calling this method
return mono_domain_get_id(domain);
}
bool _GodotSharp::is_domain_loaded() {
int32_t _GodotSharp::get_scripts_domain_id() {
return GDMono::get_singleton()->get_scripts_domain() != NULL;
MonoDomain *domain = SCRIPTS_DOMAIN;
CRASH_COND(!domain); // User must check if scripts domain is loaded before calling this method
return mono_domain_get_id(domain);
}
#define ENQUEUE_FOR_DISPOSAL(m_queue, m_inst) \
m_queue.push_back(m_inst); \
if (queue_empty) { \
queue_empty = false; \
if (!is_finalizing_domain()) { /* call_deferred may not be safe here */ \
call_deferred("_dispose_callback"); \
} \
bool _GodotSharp::is_scripts_domain_loaded() {
return GDMono::get_singleton()->is_runtime_initialized() && SCRIPTS_DOMAIN != NULL;
}
bool _GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) {
return is_domain_finalizing_for_unload(p_domain_id);
}
bool _GodotSharp::is_domain_finalizing_for_unload() {
return is_domain_finalizing_for_unload(mono_domain_get());
}
bool _GodotSharp::is_domain_finalizing_for_unload(int32_t p_domain_id) {
return is_domain_finalizing_for_unload(mono_domain_get_by_id(p_domain_id));
}
bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) {
if (!p_domain)
return true;
return mono_domain_is_unloading(p_domain);
}
bool _GodotSharp::is_runtime_shutting_down() {
return mono_runtime_is_shutting_down();
}
bool _GodotSharp::is_runtime_initialized() {
return GDMono::get_singleton()->is_runtime_initialized();
}
#define ENQUEUE_FOR_DISPOSAL(m_queue, m_inst) \
m_queue.push_back(m_inst); \
if (queue_empty) { \
queue_empty = false; \
if (!is_domain_finalizing_for_unload(SCRIPTS_DOMAIN)) { /* call_deferred may not be safe here */ \
call_deferred("_dispose_callback"); \
} \
}
void _GodotSharp::queue_dispose(MonoObject *p_mono_object, Object *p_object) {
if (GDMonoUtils::is_main_thread() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) {
_dispose_object(p_object);
} else {
#ifndef NO_THREADS
queue_mutex->lock();
#endif
// This is our last chance to invoke notification predelete (this is being called from the finalizer)
// We must use the MonoObject* passed by the finalizer, because the weak GC handle target returns NULL at this point
CSharpInstance *si = CAST_CSHARP_INSTANCE(p_object->get_script_instance());
if (si) {
si->call_notification_no_check(p_mono_object, Object::NOTIFICATION_PREDELETE);
}
ENQUEUE_FOR_DISPOSAL(obj_delete_queue, p_object);
#ifndef NO_THREADS
queue_mutex->unlock();
#endif
}
}
void _GodotSharp::queue_dispose(NodePath *p_node_path) {
if (GDMonoUtils::is_main_thread() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) {
if (GDMonoUtils::is_main_thread() && !is_domain_finalizing_for_unload(SCRIPTS_DOMAIN)) {
memdelete(p_node_path);
} else {
#ifndef NO_THREADS
@ -1020,7 +1013,7 @@ void _GodotSharp::queue_dispose(NodePath *p_node_path) {
void _GodotSharp::queue_dispose(RID *p_rid) {
if (GDMonoUtils::is_main_thread() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) {
if (GDMonoUtils::is_main_thread() && !is_domain_finalizing_for_unload(SCRIPTS_DOMAIN)) {
memdelete(p_rid);
} else {
#ifndef NO_THREADS
@ -1040,8 +1033,13 @@ void _GodotSharp::_bind_methods() {
ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread);
ClassDB::bind_method(D_METHOD("detach_thread"), &_GodotSharp::detach_thread);
ClassDB::bind_method(D_METHOD("is_finalizing_domain"), &_GodotSharp::is_finalizing_domain);
ClassDB::bind_method(D_METHOD("is_domain_loaded"), &_GodotSharp::is_domain_loaded);
ClassDB::bind_method(D_METHOD("get_domain_id"), &_GodotSharp::get_domain_id);
ClassDB::bind_method(D_METHOD("get_scripts_domain_id"), &_GodotSharp::get_scripts_domain_id);
ClassDB::bind_method(D_METHOD("is_scripts_domain_loaded"), &_GodotSharp::is_scripts_domain_loaded);
ClassDB::bind_method(D_METHOD("is_domain_finalizing_for_unload", "domain_id"), &_GodotSharp::_is_domain_finalizing_for_unload);
ClassDB::bind_method(D_METHOD("is_runtime_shutting_down"), &_GodotSharp::is_runtime_shutting_down);
ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &_GodotSharp::is_runtime_initialized);
ClassDB::bind_method(D_METHOD("_dispose_callback"), &_GodotSharp::_dispose_callback);
}

View file

@ -170,8 +170,7 @@ public:
void add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly);
GDMonoAssembly **get_loaded_assembly(const String &p_name);
_FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized; }
_FORCE_INLINE_ bool is_finalizing_scripts_domain() const { return finalizing_scripts_domain; }
_FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized && !mono_runtime_is_shutting_down() /* stays true after shutdown finished */; }
_FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; }
#ifdef TOOLS_ENABLED
@ -236,11 +235,10 @@ class _GodotSharp : public Object {
friend class GDMono;
void _dispose_object(Object *p_object);
void _dispose_callback();
List<Object *> obj_delete_queue;
bool _is_domain_finalizing_for_unload(int32_t p_domain_id);
List<NodePath *> np_delete_queue;
List<RID *> rid_delete_queue;
@ -260,10 +258,18 @@ public:
void attach_thread();
void detach_thread();
bool is_finalizing_domain();
bool is_domain_loaded();
int32_t get_domain_id();
int32_t get_scripts_domain_id();
bool is_scripts_domain_loaded();
bool is_domain_finalizing_for_unload();
bool is_domain_finalizing_for_unload(int32_t p_domain_id);
bool is_domain_finalizing_for_unload(MonoDomain *p_domain);
bool is_runtime_shutting_down();
bool is_runtime_initialized();
void queue_dispose(MonoObject *p_mono_object, Object *p_object);
void queue_dispose(NodePath *p_node_path);
void queue_dispose(RID *p_rid);

View file

@ -138,6 +138,7 @@ void MonoCache::clear_members() {
field_Image_ptr = NULL;
field_RID_ptr = NULL;
methodthunk_GodotObject_Dispose = NULL;
methodthunk_Array_GetPtr = NULL;
methodthunk_Dictionary_GetPtr = NULL;
methodthunk_MarshalUtils_IsArrayGenericType = NULL;
@ -235,6 +236,7 @@ void update_godot_api_cache() {
CACHE_FIELD_AND_CHECK(NodePath, ptr, CACHED_CLASS(NodePath)->get_field(BINDINGS_PTR_FIELD));
CACHE_FIELD_AND_CHECK(RID, ptr, CACHED_CLASS(RID)->get_field(BINDINGS_PTR_FIELD));
CACHE_METHOD_THUNK_AND_CHECK(GodotObject, Dispose, (GodotObject_Dispose)CACHED_CLASS(GodotObject)->get_method_thunk("Dispose", 0));
CACHE_METHOD_THUNK_AND_CHECK(Array, GetPtr, (Array_GetPtr)GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Array)->get_method_thunk("GetPtr", 0));
CACHE_METHOD_THUNK_AND_CHECK(Dictionary, GetPtr, (Dictionary_GetPtr)GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)->get_method_thunk("GetPtr", 0));
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IsArrayGenericType, (IsArrayGenericType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("IsArrayGenericType", 1));
@ -247,7 +249,7 @@ void update_godot_api_cache() {
CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, (DebugUtils_StackFrameInfo)GODOT_API_CLASS(DebuggingUtils)->get_method_thunk("GetStackFrameInfo", 4));
#endif
// TODO Move to CSharpLanguage::init()
// TODO Move to CSharpLanguage::init() and do handle disposal
MonoObject *task_scheduler = mono_object_new(SCRIPTS_DOMAIN, GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr());
GDMonoUtils::runtime_object_init(task_scheduler);
mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler);
@ -270,11 +272,48 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) {
}
}
// Only called if the owner does not have a CSharpInstance
// If the owner does not have a CSharpInstance...
void *data = unmanaged->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
if (data) {
return ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->value()->get_target();
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->value();
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
ERR_FAIL_COND_V(gchandle.is_null(), NULL);
MonoObject *target = gchandle->get_target();
if (target)
return target;
CSharpLanguage::get_singleton()->release_script_gchandle(gchandle);
// Create a new one
#ifdef DEBUG_ENABLED
CRASH_COND(script_binding.type_name == StringName());
CRASH_COND(script_binding.wrapper_class == NULL);
#endif
MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(script_binding.wrapper_class, script_binding.type_name, unmanaged);
ERR_FAIL_NULL_V(mono_object, NULL);
gchandle->set_handle(MonoGCHandle::new_strong_handle(mono_object), MonoGCHandle::STRONG_HANDLE);
// Tie managed to unmanaged
Reference *ref = Object::cast_to<Reference>(unmanaged);
if (ref) {
// Unsafe refcount increment. The managed instance also counts as a reference.
// This way if the unmanaged world has no references to our owner
// but the managed instance is alive, the refcount will be 1 instead of 0.
// See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
ref->reference();
}
return mono_object;
}
}
@ -304,6 +343,7 @@ MonoThread *get_current_thread() {
void runtime_object_init(MonoObject *p_this_obj) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
// FIXME: Do not use mono_runtime_object_init, it aborts if an exception is thrown
mono_runtime_object_init(p_this_obj);
GD_MONO_END_RUNTIME_INVOKE;
}

View file

@ -49,6 +49,7 @@
namespace GDMonoUtils {
typedef void (*GodotObject_Dispose)(MonoObject *, MonoObject **);
typedef Array *(*Array_GetPtr)(MonoObject *, MonoObject **);
typedef Dictionary *(*Dictionary_GetPtr)(MonoObject *, MonoObject **);
typedef MonoObject *(*SignalAwaiter_SignalCallback)(MonoObject *, MonoArray *, MonoObject **);
@ -141,6 +142,7 @@ struct MonoCache {
GDMonoField *field_Image_ptr;
GDMonoField *field_RID_ptr;
GodotObject_Dispose methodthunk_GodotObject_Dispose;
Array_GetPtr methodthunk_Array_GetPtr;
Dictionary_GetPtr methodthunk_Dictionary_GetPtr;
IsArrayGenericType methodthunk_MarshalUtils_IsArrayGenericType;

View file

@ -119,7 +119,7 @@ void SignalAwaiterHandle::_bind_methods() {
}
SignalAwaiterHandle::SignalAwaiterHandle(MonoObject *p_managed) :
MonoGCHandle(MonoGCHandle::make_strong_handle(p_managed), STRONG_HANDLE) {
MonoGCHandle(MonoGCHandle::new_strong_handle(p_managed), STRONG_HANDLE) {
#ifdef DEBUG_ENABLED
conn_target_id = 0;