From 2550cdc0c47612aa42c892083044c5c738aed8e3 Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Wed, 12 Feb 2025 19:29:30 +0100 Subject: [PATCH] [.NET] Skip serializing delegates with a disposed target When reloading assemblies, we serialize the managed delegates so we can recreate the callables later. If the delegate's target is a GodotObject that has already been disposed, we can't serialize the delegate. Before this change, trying to serialize one of these delegates throws an exception and prevents releasing its strong GCHandle, so the assembly can't be unloaded. With this change, we don't serialize the delegates and release them anyway. This means some delegates may get lost on reloading assemblies, but if their target was already freed it's probably fine. --- modules/mono/csharp_script.cpp | 10 ++++++++-- .../glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 76c3ca3cb3..963974a0ed 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -690,8 +690,14 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { if (success) { ManagedCallable::instances_pending_reload.insert(managed_callable, serialized_data); - } else if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print("Failed to serialize delegate\n"); + } else { + if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to serialize delegate.\n"); + } + + // We failed to serialize the delegate but we still have to release it; + // otherwise, we won't be able to unload the assembly. + managed_callable->release_delegate_handle(); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs index 7a88fea5f1..227bceacd7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs @@ -166,6 +166,12 @@ namespace Godot } case GodotObject godotObject: { + if (!GodotObject.IsInstanceValid(godotObject)) + { + // If the delegate's target has been freed we can't serialize it. + return false; + } + using (var stream = new MemoryStream()) using (var writer = new BinaryWriter(stream)) {