godotengine / godotengine/godot
Loading PackedScene with inherited C# Resource via LoadThreadedRequest leaks RefCounted
- Dominant language
- C++
- Stars
- 117k
- Forks
- 26.8k
- PR merge metrics
- PR metrics pending
Description
### Tested versions
Reproducible in:
- Godot Engine v4.7.1.stable.mono.official.a13da4feb
- Godot Engine v4.7.stable.mono
### System information
Windows 11 Pro - Forward+ - D3D12 - .NET / C# (v10.0.301)
### Issue description
Loading a `PackedScene` using the threaded `ResourceLoader` leaks one `RefCounted` instance when the scene contains an exported custom C# `Resource` that inherits from another custom C# `Resource`.
The leak only occurs when using:
```csharp
ResourceLoader.LoadThreadedRequest(...)
ResourceLoader.LoadThreadedGet(...)
```
The issue does **not** reproduce when:
- Loading the same scene using `GD.Load()`
- Using `ResourceLoader.Load()`
- Exporting a resource that derives directly from `Resource`
- Using built-in resources such as `Curve`
Running Godot with `--verbose` reports:
```text
Leaked instance: RefCounted: - Reference count: 0
```
### Steps to reproduce
Create the following resources:
```csharp
using Godot;
[GlobalClass]
public partial class BaseResource : Resource
{
}
```
```csharp
using Godot;
[GlobalClass]
public partial class DerivedResource : BaseResource
{
}
```
Create a scene with the following script:
```csharp
using Godot;
public partial class Empty : Node
{
[Export]
private DerivedResource _resource = null!;
}
```
Assign a built-in instance of `DerivedResource` in the Inspector.
Load the scene using the threaded loader:
```csharp
const string path = "res://empty.tscn";
var error = ResourceLoader.LoadThreadedRequest(path);
while (ResourceLoader.LoadThreadedGetStatus(path)
!= ResourceLoader.ThreadLoadStatus.Loaded)
{
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
}
var scene = ResourceLoader.LoadThreadedGet(path);
GD.Print(scene);
```
Close the application.
---
# Expected behavior
The application should exit without reporting leaked objects.
---
# Actual behavior
On shutdown Godot reports:
```text
WARNING: 1 ObjectDB instance was leaked at exit
(run with `--verbose` for details)
Leaked instance: RefCounted: - Reference count: 0
```
---
# Additional observations
The issue **does not** reproduce with a resource that derives directly from `Resource`:
```csharp
using Godot;
[GlobalClass]
public partial class DirectResource : Resource
{
}
```
```csharp
using Godot;
public partial class Empty : Node
{
[Export]
private DirectResource _resource = null!;
}
```
The issue also **does not** reproduce when using built-in resources, for example:
```csharp
using Godot;
public partial class Empty : Node
{
[Export]
private Curve _curve = null!;
}
```
Likewise, replacing the threaded loading code with:
```csharp
var scene = GD.Load("res://empty.tscn");
```
does **not** produce the leak.
I reduced this issue from a much larger project to this minimal reproduction. The project contains only the classes shown above and consistently reproduces the leak on both Godot 4.7 and 4.7.1 Mono.
### Minimal reproduction project (MRP)
[DerivedResourceBug.zip](https://github.com/user-attachments/files/30617032/DerivedResourceBug.zip)
Contributor guide
Research direction
Start by running the attached DerivedResourceBug.zip minimal reproduction on the affected Mono versions with verbose logging. Compare the threaded ResourceLoader.LoadThreadedRequest/LoadThreadedGet path with the synchronous loading cases described in the issue. Done means the inherited C# Resource scene loads without an ObjectDB leak at shutdown while the documented non-reproducing cases remain unaffected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, godot
- Domain
- game-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100