mirror of
https://github.com/stan220/gkj-binding-leak-poc.git
synced 2026-09-08 15:28:56 +00:00
43 lines
1.6 KiB
Markdown
43 lines
1.6 KiB
Markdown
# godot-kotlin-jvm binding leak PoC
|
|
|
|
Repro project for [utopia-rise/godot-kotlin-jvm#941](https://github.com/utopia-rise/godot-kotlin-jvm/issues/941).
|
|
|
|
Minimal reproduction for a JVM-side binding leak in godot-kotlin-jvm 0.16.2
|
|
(present since the MemoryManager rework, still present on master): every
|
|
non-RefCounted `Object` freed at runtime leaves its entry in
|
|
`MemoryManager.ObjectDB` forever. For Objects the binding holds a strong
|
|
reference to the wrapper, so the JVM heap grows without bound while native
|
|
memory stays flat.
|
|
|
|
## Run
|
|
|
|
Requires a godot-kotlin-jvm 0.16.2-4.6.3 editor binary and a JDK 17+.
|
|
|
|
```bash
|
|
./gradlew build
|
|
godot --headless --import .
|
|
godot --headless --import . # run twice, first pass may bail early
|
|
godot --headless --path . res://main.tscn
|
|
```
|
|
|
|
Output on 0.16.2-4.6.3 (macOS, headless):
|
|
|
|
```
|
|
[POC] freed=20000 nativeObjects=1472 jvmObjectDB=20039
|
|
[POC] LEAK CONFIRMED: ~18567 zombie bindings in MemoryManager.ObjectDB
|
|
```
|
|
|
|
All 20000 freed nodes are still in the JVM ObjectDB. How the node is freed
|
|
does not matter: `free()` from Kotlin, `queueFree()`, or engine-side deletion
|
|
all leak.
|
|
|
|
## Root cause
|
|
|
|
`KotlinBindingManager::_instance_binding_free_callback` queues the dead object
|
|
via `MemoryManager::queue_dead_object(obj)`, which reads
|
|
`obj->get_instance_id()`. Godot calls instance-binding free callbacks at the
|
|
very end of `Object::~Object`, after `ObjectDB::remove_instance(this);
|
|
_instance_id = ObjectID();` (core/object/object.cpp, same order in every 4.x
|
|
release since 4.3). The id is always the null ObjectID by then, so the JVM is
|
|
told to remove `ObjectID(0)` instead of the real id and the real entry stays.
|