-
-
Notifications
You must be signed in to change notification settings - Fork 21.8k
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
.NET: Failed to unload assemblies. Please check <this issue> for more information. #78513
Comments
Something was wrong with one of my [Tool] class in code after upgrade from 4.0 to 4.1 ("This class does not inherit from Node") and got this error. I just changed it to Node3D and back, and then the "cache" bug got fixed magicly? |
The workaround for this using this library is also copied below:
As far as I'm aware Godot doesn't provide an event for signalling when your dll/plugin is about to be unloaded, so you essentially need to call the above solution after every serialization/deserialization. |
You can use the normal |
Thanks for the info, I somehow missed that. As an FYI, I played around with your solution trying it on a My solution was to place the code within a
This ensures it is not dependent on any node(s) and is always registered only once, and re-registered upon reloading the assembly. |
I am not using The only library I am referencing is one I have authored. My
That project's csproj is dead simple, with no other project references. @RedworkDE, you mentioned "does not violate any guidelines"; what are these guidelines? Is there a documentation page? Something else? Thanks. |
It refers to Microsoft's docs page that was linked a bit further up: https://learn.microsoft.com/en-us/dotnet/standard/assembly/unloadability#troubleshoot-unloadability-issues Also all these really also have to apply to the library, but for most libraries there isn't too much you can do about it (except not use the library) |
I am hitting this issue and I'm fairly confident it is not my (direct) fault. I can reliably cause this error to appear in the console by running a C# rebuild while an offending Scene is loaded in the editor:
If I build again, I will get the "Failed to unload assemblies" error. If I close the offending scene and run the rebuild again, I rebuild without any issues and everything is fine. I've been trying to figure out precisely what's needed to get this problem to repro. It seems connected to the new |
You also seem to be using generic |
Yeah, it seems that's the root cause. That would be very unfortunate if true. I have a micro reproduction project and I'll write up another issue. It runs fine but for the occasional compilation issue. |
I have created that reproduction project: #79519 |
I don't know what is going on or why but I started getting this error today in Godot 4.1.1 .NET version. It's completely blocking me from doing any development. I've spent all day trying to hunt down weird behaviors. It seems that when this issue happens it's corrupting properties in the editor - or possibly inherited scenes are breaking. |
Slight update. I'm just guessing here, but I suspect the issue might be somehow related to the new GlobalClass. That's all I can think of. I got rid of the errors by deleting a node which had either been added via GlobalClass node or it was a regular node with the same script attached to it. Either way, I deleted it and the error was gone. I added it back in as GlobalClass node - the error stayed gone. |
I can absolutely understand that. The problems since 4.1 are unfortunately unexpected, varied and sometimes difficult to analyse. The new As described above, some problems are related to The simplest workaround is probably to close the problematic scene tabs (mostly Otherwise, try the code listed at the top. Surprisingly, the following worked for me:
With this code, the unload works after an incorrect attempt. |
I added generic nodes today, and I started getting this issue. |
I'm getting this issue in 4.1, and I'm not using any third-party libraries, nor am I using generic nodes, nor am I using the Well, OK. I used to have a C# script that had both of those attributes, but I've since rewritten it in GDScript, so it shouldn't be relevant anymore...right? Is there any chance that the ghost of the C# version of that script still lives on? Maybe in some cache somewhere? |
Also happens whenever I enable a C# editor plugin, and only goes away once I disable the plugin and restart the editor. |
The new
I will try today the method mentioned above by Quinn-L and put the unload handler in an |
Nope. We decided not to waste so much time with this problem and rewrite certain parts of the code in |
I think I figured out what was causing it for me. It wasn't editor plugins, global classes, or tool scripts. No, it's because I had more than one Node class in a single script file. namespace FlightSpeedway
{
public partial class Player : CharacterBody3D
{
private PlayerState _currentState;
private Vector3 _spawnPoint;
private Vector3 _spawnRotation;
public void ChangeState<TState>() where TState : PlayerState
{
GD.Print($"Changing state to {typeof(TState).Name}");
_currentState?.OnStateExited();
foreach (var state in States())
{
state.ProcessMode = ProcessModeEnum.Disabled;
}
_currentState = States().First(s => s is TState);
_currentState.ProcessMode = ProcessModeEnum.Inherit;
_currentState.OnStateEntered();
}
private IEnumerable<PlayerState> States()
{
for (int i = 0; i < GetChildCount(); i++)
{
var child = GetChild<Node>(i);
if (child is PlayerState state)
yield return state;
}
}
}
public partial class PlayerState : Node
{
protected Player _player => GetParent<Player>();
protected Node3D _model => GetNode<Node3D>("%Model");
public virtual void OnStateEntered() {}
public virtual void OnStateExited() {}
}
} Once I moved In case this is relevant, there are other node classes(such as |
|
I can't confirm, we have all classes in separate files and as soon as |
I'm not saying those scripts don't trigger the problem; I'm saying that multiple nodes in one file also triggers this problem. |
There are multiple causes of this problem. From the comments, there are at least three ways you can run into the issue.. |
This comment was marked as off-topic.
This comment was marked as off-topic.
@ghtyrant You may check if you are using any unload-incompatible featrues (System.Text.Json, or the Example above) in editor reachable code ( |
@Delsin-Yu Thanks a lot. I went through all of my code again and noticed I missed two exported variables that were not properties. Now I can rebuild my project without restarting Godot. Any plans on adding a warning/error messages for such cases? |
@ghtyrant That sounds like a relatively heavy task that requires a dedicated Roslyn analyzer to go through all code paths that the editor may touch and perform syntax tree analysis; you are welcome to open up a proposal in the proposal repo and see if any community contributor is interested. |
Folks, |
Has any solution to this been found (or proposed to be fixed in the next update)? I'm running into this issue myself and I'm unsure what exactly is causing it. If it's a tool script ok. But I'd like to know what KIND of thing can cause this. Cuz at the moment it's a little confusing. |
Nothing? |
I was playing around with a [Tool]
public partial class BodyPartEditor : Node2D
{
[Export(PropertyHint.File, "*.tres")]
public string ResourcePath { get; set; }
[ExportToolButton("Save Resource")]
public Callable SaveResourceButton { get; set; }
[ExportToolButton("Load Resource")]
public Callable LoadResourceButton { get; set; }
public override void _Ready()
{
SaveResourceButton = Callable.From(SaveResource);
LoadResourceButton = Callable.From(LoadResource);
// ...
}
} I am not sure if I use the |
@jsbeckr I am having the same problem with the [Tool]
[GlobalClass]
public partial class DatasourceSqlDb : DatasourceBase<SqlConnection, DatasourceConfigSqlDb> {
[ExportGroup("Connection")]
[Export]
public override DatasourceConfigSqlDb Config { get; set; }
[ExportToolButton("Test Connection")]
public Callable TestConnectionButton => Callable.From(this.TestConnection);
...
} |
I spend 2 month on an addon. I actually planned to release it next week but as I use System.Text.Json, other dll libs, threads, async await + some more unidentified things that cause ".NET: Failed to unload assemblies." my addon now causes that error with every press of build. I can't release an addon that causes this issue whenever build is pressed!!!! From what I've read there is no usable solution yet... Please!!!! At least make a build signal or smth so I can use that to deactivate the addon/addons scripts temporarily... otherwise this addon I spent so much time on will not be usable.... I actually have worked on 2 more that just needed some polishing. I planned to release those within the year but with this error around those would also become useless...... ----- edit: I've worked on the mentioned workaround for the .net disconnect issue with chatgpt, but I don't know any c++ and have no clue if this would actually work. It would be nice if someone familiar with godots source could take a look at it: The idea is to make the build send a signal. Addons could then unload .net stuff that causes the .net disconnect. When the addons are finished unloading they would send a "I'm done signal" and godot would then start the build process. When finished there would be a "finished building signal" and the addons would load back what was unloaded before. To refine this further addons could subscribe to the build pause thing, then godot could check for all subscribed addons to finish unloading. So that addons that don't need to unload anything won't make godot pause the build process. |
@BrahRah As for unloading static caches produced by public static class JsonCacheControl
{
public static void ClearCache()
{
var assembly = typeof(System.Text.Json.JsonSerializerOptions).Assembly;
var updateHandlerType = assembly.GetType("System.Text.Json.JsonSerializerOptionsUpdateHandler");
var clearCacheMethod = updateHandlerType?.GetMethod("ClearCache", BindingFlags.Static | BindingFlags.Public);
clearCacheMethod?.Invoke(null, [null]);
}
} |
@Delsin-Yu Doesn't ISerializationListener only exe when a scene is loaded or saved. Does it really also run when build project (Alt+B) is pressed? |
@BrahRah From what I observed and used throughout my project, no, the Engine calls QQ2025219-52550.mp4In the example, I'm using a tool script for the demo, but an editor plugin script would also do the job. Put the global-wise unloading code there so that you do not need to leave the specific scene open all the time. QQ2025219-53635.mp4An additional example that shows the unloading does indeed happen is the HashCode for the "current" object has changed between calls. Behind the scenes, the object and the DLL it belongs get unloaded and destroyed, and the engine reloads the newly built DLL and then instantiates the object and calls into it. |
@Delsin-Yu Would this also work with static classes tho? Form what I understand static classes cannot implement interfaces. |
@BrahRah Direct Call ApproachCall into the methods from the static class you wish to perform unload preprocessing/reload postprocessing. QQ2025219-21458.mp4Reflection Call ApproachUse custom attribute with reflection to invoke the unload & reload methods dynamically. UnloadHelper.cs #if TOOLS
using Godot;
using System;
using System.Linq;
using System.Reflection;
namespace UnloadUtils;
[Tool]
public partial class UnloadHelper : EditorPlugin, ISerializationListener
{
void ISerializationListener.OnBeforeSerialize()
{
GD.Print("UnloadHelper.OnBeforeSerialize");
InvokeSerializationListenerMethod("PerformUnload");
}
void ISerializationListener.OnAfterDeserialize()
{
GD.Print("UnloadHelper.OnAfterDeserialize");
InvokeSerializationListenerMethod("PerformReload");
}
private static void InvokeSerializationListenerMethod(string methodName)
{
foreach (var unloadableType in AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes())
.Where(type => type.GetCustomAttribute<SerializationListenerAttribute>() != null))
{
unloadableType
.GetMethods(
BindingFlags.Static
| BindingFlags.Public
| BindingFlags.NonPublic)
.FirstOrDefault(method => method.Name == methodName)
?.Invoke(null, null);
}
}
}
#endif
[AttributeUsage(AttributeTargets.Class)]
public class SerializationListenerAttribute : Attribute; MyStaticClass.cs using Godot;
using UnloadUtils;
namespace SerializationListener;
[SerializationListener]
public static class MyStaticClass
{
private static void PerformUnload()
{
GD.Print("MyStaticClass.PerformUnload");
}
private static void PerformReload()
{
GD.Print("MyStaticClass.PerformReload");
}
} Event Subscription ApproachSubscribe to the corresponding event delegates with static classes' module initializer. UnloadHelper.cs #if TOOLS
using Godot;
using System;
namespace UnloadUtils;
[Tool]
public partial class UnloadHelper : EditorPlugin, ISerializationListener
{
public static event Action OnBeforeSerialize;
public static event Action OnAfterDeserialize;
void ISerializationListener.OnBeforeSerialize()
{
GD.Print("UnloadHelper.OnBeforeSerialize");
OnBeforeSerialize?.Invoke();
}
void ISerializationListener.OnAfterDeserialize()
{
GD.Print("UnloadHelper.OnAfterDeserialize");
OnAfterDeserialize?.Invoke();
}
}
#endif MyStaticClass.cs using Godot;
namespace SerializationListener;
public static class MyStaticClass
{
#if TOOLS
[System.Runtime.CompilerServices.ModuleInitializer]
internal static void Register()
{
UnloadUtils.UnloadHelper.OnBeforeSerialize += DoUnloadStuff;
UnloadUtils.UnloadHelper.OnAfterDeserialize += DoReloadStuff;
}
#endif
private static void DoUnloadStuff()
{
GD.Print("MyStaticClass.DoUnloadStuff");
}
private static void DoReloadStuff()
{
GD.Print("MyStaticClass.DoReloadStuff");
}
} |
Thx that would fix the .net disconnect at least partially. Wired that nobody is providing such an addon yet. |
The .Net module of Godot 4 is relatively new, so it's common to have fewer community resources; at least, people will see our discussions when they encounter similar issues and visit this thread. |
Does the:
Need to be used per class or does it work globally for all classes? |
It works globally, you may check the source code here // Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
[assembly: MetadataUpdateHandler(typeof(JsonSerializerOptionsUpdateHandler))]
#pragma warning disable IDE0060
namespace System.Text.Json
{
/// <summary>Handler used to clear JsonSerializerOptions reflection cache upon a metadata update.</summary>
internal static class JsonSerializerOptionsUpdateHandler
{
public static void ClearCache(Type[]? types)
{
// Ignore the types, and just clear out all reflection caches from serializer options.
foreach (KeyValuePair<JsonSerializerOptions, object?> options in JsonSerializerOptions.TrackedOptionsInstances.All)
{
options.Key.ClearCaches();
}
DefaultJsonTypeInfoResolver.ClearMemberAccessorCaches();
}
}
} |
I've implemented it and dono if it works or not. As I mentioned I have multiple things that cause the .net disconnect issue not just the text.json
Is there a way to find what exactly causes the .net disconnect or do we have to slowly find everything? |
As I mentioned here
However, a no-brainer tooling (analyzer) does not yet exist. |
Lets make a list then.
How is this as a start? Also where should we put this? I've been digging a bit more into it and even using generic Node causes this... |
@BrahRah Based on your level of knowledge, the |
Godot version
Any 4.x version
Issue description
Assembly reloading can fail for various reasons, usually because a library used in tools code is not compatible with assembly unloading.
After unloading has failed, C# scripts will be unavailable until the editor is restarted (in rare cases it may be possible to complete the unloading by re-building assemblies after some time).
If assembly unloading fails for your project check Microsoft's troubleshooting instructions and ensure that you are not using one of the libraries known to be incompatible:
If you know of additional libraries that cause issues, please leave a comment.
If your code doesn't use any libraries, doesn't violate any guidelines and you believe unloading is blocked by godot, please open a new issue. Already reported causes are:
Minimal reproduction project & Cleanup example
Footnotes
Bugsquad edit ↩ ↩2
The text was updated successfully, but these errors were encountered: