godot: .NET: Failed to unload assemblies. Please check for more information.

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

using Godot;
using System;

[Tool]
public partial class UnloadingIssuesSample : Node
{
    public override void _Ready()
    {
        // block unloading with a strong handle
        var handle = System.Runtime.InteropServices.GCHandle.Alloc(this);

        // register cleanup code to prevent unloading issues
        System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(System.Reflection.Assembly.GetExecutingAssembly()).Unloading += alc =>
        {
            // handle.Free();
        };
    }
}

[^1]: Bugsquad edit

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 49
  • Comments: 91 (31 by maintainers)

Commits related to this issue

Most upvoted comments

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. Restarting the editor is good for one run. But I keep constantly having to restart. This is 100% blocking all game development for me.

Without fixing this bug, the use of version 4.1.x is significantly limited and currently not recommended. We are using 4.0 until this is fixed, but are mourning the new features of 4.1 that we would have loved to use. Hopefully a fix in 4.2?

Also happens whenever I enable a C# editor plugin, and only goes away once I disable the plugin and restart the editor.

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:

modules/mono/glue/runtime_interop.cpp:1324 - System.ArgumentException: An item with the same key has already been added. Key: BehaviorActorView`1[LeshonkiView]
     at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
     at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
     at Godot.Bridge.ScriptManagerBridge.ScriptTypeBiMap.Add(IntPtr scriptPtr, Type scriptType) in /root/godot/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs:line 23
     at Godot.Bridge.ScriptManagerBridge.TryReloadRegisteredScriptWithClass(IntPtr scriptPtr) in /root/godot/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs:line 579

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 [GlobalClass] attribute and resources, but not necessarily on a one-to-one basis.

I got this while using Newtonsoft. I would really appreciate this having high priority, as this is quite a breaking bug. Are there plans to address this? Also, can someone explain to me in laymen’s terms how to just get my project not to scream at me when using newtonsoft? Is there a bit of code I can just tack onto the end of my JSON manager class? I saw the bit about on load but unsure of the context this is used

Even after removing all initial values off properties and fields, I am still seeing this issue when developing C# editor plugins and tool scripts.

I realize that I am not adding a lot to the conversation – just wanted to document that I could not verify the suggested fix on the 4.2 dev versions (macOS).

Godot 4.2.1. I keep getting “.NET: Failed to unload assemblies.” in tool scripts randomly, this completely blocks my work. Nothing of the mentioned above sheds light on what is the problem. No idea how to determinate the problem or reproduce it on demand. It’s a dead end making Godot NET unusable.

I am not using Json.NET or System.Text.Json, and I am experiencing this issue. In the worst case, it results in data loss as serialized properties from C# Node scripts are reset to default.

The only library I am referencing is one I have authored. My .csproj file looks like this:

<Project Sdk="Godot.NET.Sdk/4.1.0">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <EnableDynamicLoading>true</EnableDynamicLoading>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Lib\Hawthorn\Hawthorn\Hawthorn.csproj" />
  </ItemGroup>
</Project>

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.

There are quite a few possible issues causing assemblies to fail to unload.

Some are out of our control.

  • Other libraries aren’t friendly to being unloaded (cf. original message).
  • Your game code is not friendly to being unloaded. It is less common, but still possible.
  • In both cases, some features like dynamic are known to be basically incompatible with assembly unloading.

Some are bugs in Godot. As of now, the following have been identified.

  • Fix is currently in 4.3.dev
  • Fix is to be merged, but waiting
    • #81903 (edit on April 5th: this is now also merged)

I’ll edit if I remember other linked issues.

This is wild, I can’t build and run more than once on our GGJ code (https://github.com/pdxparrot/ggj2024) without running into this. I’m having to restart the editor every time I do anything and if I don’t then it corrupts whatever scene I was working on. This is making 4.2 unusable, is there any way to escalate this as a full on show stopper problem?

Your issue is related with #79519, a temporary fix (#86305) exits, I will try to validate if the fix works with the project you shared. This blocking issue affects every 4.x versions, which I think is crucial to fix before anyone starts working on a large-scale project in C#.

Can confirm that #86305 does fix the issue (Generic Class Duplicate Entries).

Here are patched version of GodotSharp.dll that includes the fix. Do remember to backup your project/editor to prevent data loss. 4.2.1.zip 4.2.0.zip cc @Luminoth

There are multiple causes of this problem. From the comments, there are at least three ways you can run into the issue…

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 PlayerState to a separate file, the issue stopped happening.

In case this is relevant, there are other node classes(such as PlayerFlyState) that inherit PlayerState. Those nodes exist as children of the player node, and they get enabled/disabled as the player changes states.

It’s not in 4.3.dev5, it will be in dev6 which should release in a few days

I compiled the latest godot from the master branch, but I still encounter this problem. I can only solve it by binding the event this way: xxxx.Connect(SignalName.Pressed, new Callable(this, MethodName.XXXX)

  1. Do not use += to bind events
  2. Do not use anonymous functions

If possible, can you publish an issue about this, and, include a MRP (minimal reproducible project) for this issue you are encountering? Or, just share the code snippet that causes the issue?

I’m very sorry, I found that I didn’t compile the mono version of godot through the correct process. At present, I don’t know how to compile GodotSharp, so I directly copied the old GodotSharp folder and used it with the new godot binary, so my test results is not credible. I will try again when 4.3-dev6 is officially released. This is my test code:

using Godot;

[Tool]
public partial class Main : GridContainer
{
        public override void _Ready()
        {

                for (int i = 0; i < 9; ++i)
                {
                        Button button = new()
                        {
                                Text = i.ToString(),
                        };

                        int id = i;
                        button.Pressed += () =>
                        {
                                OnButtonPressed(id);
                        };

                        AddChild(button);
                }
        }

        private void OnButtonPressed(int id)
        {
                GD.Print($"Button {id}");
        }
}

My testing process is:

  1. Start godot and open the main scene, making sure that the editor renders the content of the scene.
  2. Start vscode, modify a little bit of code, and click the run button in godot editor to compile and run the code.

See the .Net specific page for Compiling the Engine.

Thank you! I can confirm that the new version has indeed fixed this issue.

It’s not in 4.3.dev5, it will be in dev6 which should release in a few days

This is wild, I can’t build and run more than once on our GGJ code (https://github.com/pdxparrot/ggj2024) without running into this. I’m having to restart the editor every time I do anything and if I don’t then it corrupts whatever scene I was working on. This is making 4.2 unusable, is there any way to escalate this as a full on show stopper problem?

Huge thanks @Rylius! I was in the exact same case where I used to initialize my exported properties like this:

[Export]
public MyCustomResource MyProperty { get; set; } = new() { ResourceLocalToScene = true };

Removing the initialization from the code and doing it in the editor with the inspector instead worked like a charm! The issue in my project is gone now. Custom resources I have made so far were annotated with both [Tool] and [GlobalClass] if it helps.

You can use the normal AssemblyLoadContext.Unloading event to trigger such cleanup code. I unfolded the code example in the initial post demonstrating its usage.

Thanks for the info, I somehow missed that.

As an FYI, I played around with your solution trying it on a EditorPlugin derived class, but didn’t find it reliable in on _Ready. As far as I could tell (or maybe I tested it wrong), rebuilding would reload the C# project but not re-invoke _Ready causing the error on subsequent rebuilds since the ‘new’ Unloading event is not registered (I assume because the node itself is not removed and re-added).

My solution was to place the code within a [ModuleInitializer] method, ie:

internal class AppModule
{
    [System.Runtime.CompilerServices.ModuleInitializer]
    public static void Initialize()
    {
        System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(System.Reflection.Assembly.GetExecutingAssembly()).Unloading += alc =>
        {
            var assembly = typeof(JsonSerializerOptions).Assembly;
            var updateHandlerType = assembly.GetType("System.Text.Json.JsonSerializerOptionsUpdateHandler");
            var clearCacheMethod = updateHandlerType?.GetMethod("ClearCache", BindingFlags.Static | BindingFlags.Public);
            clearCacheMethod?.Invoke(null, new object?[] { null });

            // Unload any other unloadable references
        };
    }
}

This ensures it is not dependent on any node(s) and is always registered only once, and re-registered upon reloading the assembly.

It’s not in 4.3.dev5, it will be in dev6 which should release in a few days

I compiled the latest godot from the master branch, but I still encounter this problem. I can only solve it by binding the event this way: xxxx.Connect(SignalName.Pressed, new Callable(this, MethodName.XXXX)

  1. Do not use += to bind events
  2. Do not use anonymous functions

If possible, can you publish an issue about this, and, include a MRP (minimal reproducible project) for this issue you are encountering? Or, just share the code snippet that causes the issue?

I’m very sorry, I found that I didn’t compile the mono version of godot through the correct process. At present, I don’t know how to compile GodotSharp, so I directly copied the old GodotSharp folder and used it with the new godot binary, so my test results is not credible.

I will try again when 4.3-dev6 is officially released.

This is my test code:

using Godot;

[Tool]
public partial class Main : GridContainer
{
        public override void _Ready()
        {

                for (int i = 0; i < 9; ++i)
                {
                        Button button = new()
                        {
                                Text = i.ToString(),
                        };

                        int id = i;
                        button.Pressed += () =>
                        {
                                OnButtonPressed(id);
                        };

                        AddChild(button);
                }
        }

        private void OnButtonPressed(int id)
        {
                GD.Print($"Button {id}");
        }
}

My testing process is:

  1. Start godot and open the main scene, making sure that the editor renders the content of the scene.
  2. Start vscode, modify a little bit of code, and click the run button in godot editor to compile and run the code.

This issue has been bothering me for a long time. Today I found the root cause through continuous attempts. It is caused by the use of anonymous functions when binding signals!

Can we please recentre on topic please? This thread is already very long, the future GDExtension implementation is unrelated to the issue at hand here.

FWIW, the error you’re experiencing is due to the deserialization of callables happening late after the state of your objects is restored. I have already started to look at how we could fix those timing issues during our assembly reload process last week. I need to run more tests on exactly how to improve this without changing the current behaviour.

Can anyone confirm any of the existing reproduction methods still trigger this?

AFAIK all the obvious cases we have highlighted until now should be fixed now.

Probably yes, because the C# script must also be tool code in order to be able to access its properties from GDScript.

Wow, that is a rabbit hole. So far though it seems to be working, thank you so much for that!

I’m pretty sure the answer on this is going to be no, but is there a way for me to check in my code that the patched version of that DLL is being used? To like kick up an error or something if one of my teammates forgets to apply it?

@Luminoth

My suggestion is to add the editor itself into the version control system so that any file changes can appear and automatically update to the latest version when they merge your commit. But do remember that this patch is not yet accepted into the mainstream, and we have not thoroughly tested it, issues may occur, so do remember to make backups.

I have a weird bug when “Failed to unload assemblies” happen. My currently open scene will be resaved with weird or null value in the field. Example of corrupted value:

Object(Container,"_import_path":NodePath(""),"unique_name_in_owner":true,"process_mode":0,"process_priority":0,"process_physics_priority":0,"process_thread_group":0,"editor_description":"","visible":true,"modulate":Color(1, 1, 1, 1),"self_modulate":Color(1, 1, 1, 1),"show_behind_parent":false,"top_level":false,"clip_children":0,"light_mask":1,"visibility_layer":1,"z_index":0,"z_as_relative":true,"y_sort_enabled":false,"texture_filter":0,"texture_repeat":0,"material":null,"use_parent_material":false,"clip_contents":false,"custom_minimum_size":Vector2(499, 373),"layout_direction":0,"layout_mode":2,"size_flags_horizontal":4,"size_flags_vertical":4,"size_flags_stretch_ratio":1.0,"auto_translate":true,"localize_numeral_system":true,"tooltip_text":"","focus_neighbor_left":NodePath(""),"focus_neighbor_top":NodePath(""),"focus_neighbor_right":NodePath(""),"focus_neighbor_bottom":NodePath(""),"focus_next":NodePath(""),"focus_previous":NodePath(""),"focus_mode":0,"mouse_filter":1,"mouse_force_pass_scroll_events":true,"mouse_default_cursor_shape":0,"shortcut_context":null,"theme":null,"theme_type_variation":&"","script":Resource("res://Scenes/Arena/Battlefield/Grid.cs"),"GridSize":Vector2i(8, 6),"SlotSize":58.0,"SlotSpacing":5.0,"IsOnPlayerGrid":true,"Slot":Resource("res://Scenes/Arena/Battlefield/GridSlot.tscn"),"gridSize":Vector2i(8, 6),"slotSize":58.0,"slotSpacing":5.0,"isOnPlayerGrid":true,"slot":Resource("res://Scenes/Arena/Battlefield/GridSlot.tscn"))

I mostly report this error for search result purpose. If I have time, I will try to make a repro project.

I just want to add to this that on 4.2 I also had this same issue with the following code in a custom resource:

[Export]
public ItemStats Stats = new ItemStats();

The fix as @Rylius said is as follows:

[Export]
public ItemStats Stats = null;

I then init the export either through editor or from an init function. It’s worth noting that this resource is not marked as [Tool] or [GlobalClass], just a normal resource script.

Thanks for the workaround!

@RedworkDE Could you maybe edit #81903 into the initial post? It seems to be a reliable way to trigger this issue.

Are you sure it was new() that was the problem, and not the fact that it was being initialized in the first place? If you actually assign it a value in the inspector, does the problem come back?

Yes. I’ve gone through my commit history one by one to figure out what caused this issue (somehow didn’t happen immediately/might not have edited any code for a few commits) and everything was fine just before I added the new() (the GlobalClass resources existed for several weeks before that). Removing it immediately fixed the issue. With null! the inspector just shows “<empty>” (as expected), and assigning a new resource or loading an existing saved one work perfectly fine.

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. Maybe this is helpful or maybe it is not, but I thought I’d mention it. It was a nightmare to track down.

@Tichau If possible, can you publish an issue about this, and, include a MRP (minimal reproducible project) for this issue you are encountering? Or, just share the code snippet that causes the issue?

It’s not in 4.3.dev5, it will be in dev6 which should release in a few days

I compiled the latest godot from the master branch, but I still encounter this problem. I can only solve it by binding the event this way: xxxx.Connect(SignalName.Pressed, new Callable(this, MethodName.XXXX)

  1. Do not use += to bind events
  2. Do not use anonymous functions

If possible, can you publish an issue about this, and, include a MRP (minimal reproducible project) for this issue you are encountering? Or, just share the code snippet that causes the issue?

I’m very sorry, I found that I didn’t compile the mono version of godot through the correct process. At present, I don’t know how to compile GodotSharp, so I directly copied the old GodotSharp folder and used it with the new godot binary, so my test results is not credible.

I will try again when 4.3-dev6 is officially released.

This is my test code:

using Godot;

[Tool]
public partial class Main : GridContainer
{
        public override void _Ready()
        {

                for (int i = 0; i < 9; ++i)
                {
                        Button button = new()
                        {
                                Text = i.ToString(),
                        };

                        int id = i;
                        button.Pressed += () =>
                        {
                                OnButtonPressed(id);
                        };

                        AddChild(button);
                }
        }

        private void OnButtonPressed(int id)
        {
                GD.Print($"Button {id}");
        }
}

My testing process is:

  1. Start godot and open the main scene, making sure that the editor renders the content of the scene.
  2. Start vscode, modify a little bit of code, and click the run button in godot editor to compile and run the code.

See the .Net specific page for Compiling the Engine.

This issue has been bothering me for a long time. Today I found the root cause through continuous attempts. It is caused by the use of anonymous functions when binding signals!

This issue has fixed in 4.3 you may try it out if the fix works. https://github.com/godotengine/godot/pull/83217

Been iterating on my game quite a bit since yesterday, after building the latest with all of the fixes, and got another unload assembly crash in editor today.

It is giving me a lot of logs pointing back to NativeUtils, DelegateUtils and GodotObject. Unload_Assembly_Crash_v4.3.dev.mono.custom_build [febb11f8a].txt

The referenced “RadialPatternResolver” is a Tool script that contains a reference to a GlobalClass Resource.

If possible, can you publish an issue about this, and, include a MRP (minimal reproducible project) for this issue you are encountering?

I got the assembly unload error about 2-ish weeks ago after working in the Godot editor for quite a while on various scenes in a row and hit the build button as I needed to assign some new properties I had just added. That was after I fixed obvious cases in my code where I ended up initializing static variables due to autoloads running in the editor. So there likely isn’t easily triggerable bug left in Godot that makes this issue show up, but it seems plausible there is still some way to end up triggering this issue. I can’t remember if I had already switched to 4.2.2 RC 2.

After finding this I noticed this error appear while VSCode is open. Closing it stopped the errors. And, It did seem to start after I tried setting up a debugging environment in VSCode (unsuccessfully), now I’m trying to figure out how to revert what I did 😕

I found asituation that would trigger this issue almost 100%,. MemroyPack is agood library, it’s Aot friendly, and it won’t have any problens with the editor when used in Godot,but once you use a serialized data type with a [MemoryPackable] annotation as a member field of Godot script that inherits from node or its subclasses, it will cause Godot to be unable to uninstall them.

example: &------------------------------------------------ namespace GodotDieFlowerUI { public partial class Graphics : Panel {

	[Export]
	public OptionButton gameReSolution{get;set;}
	public Resolution gameReSolutionData{get;set;}  

      .....................

& -------------------------------------- using MemoryPack;

namespace godotDieFlowerImplement { [MemoryPackable] public partial struct Resolution

therefore, to be on the safe side,it is best to isolate all intrusive code from external libraries.

There might be better solutions out there, but for me these two simple scripts worked perfectly

Newtonsoft is a better solution, it’s what I use. Installing it is very easy as well.

So here’s a strange one:

I get immediate assembly unload in an empty project with just a simple GD.Print("Hello world"); in the _Ready function once I build the project twice.

But only if I start the executable Godot_v4.1.2-stable_mono_linux.x86_64 directly in KDE by double clicking. If I drop to a terminal and do ./Godot_v4.1.2-stable_mono_linux.x86_64 - no more assembly unloading.

Is there a reason why no attached console could cause this?

Just wanting to add more to this conversation. I definitely started running into this issue when using the [GlobalClass] attribute. More specifically when I used this on Resource Classes that utilize generic variables. Once I determined the issue in my case I would restart the editor and the error would not be present. Everything seemed to work as expected unless I edit the scripts that either implement [GlobalClass] and or scripts that utilize [Export] of those resources. Not ideal but It would result in me restarting the editor and continuing on with my work. I am wondering what are the implications on builds is this just an issue with the editor? Im still early on in my project and want to know if I should rework the scripts / remove the [GlobalClass] attribute and just assign the script to an instanced resource as the work around.

CORRECTION: After further investigation, it doesn’t matter what scripts I edit. I receive this error anytime I edit a script and click build. It seems that as long as I am not trying to EXPORT any variables / anything that affects the editor directly the changes still save, and I am able to run the game without issue. So I am finding I only need to reload the project if the editor needs to be updated.

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 added generic nodes today, and I started getting this issue.

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.

You also seem to be using generic Nodes which also cause issues in some cases, see https://github.com/godotengine/godot/pull/79007