godot: Crash when connecting or disconnecting a signal from multiple threads
Godot version: v3.2.beta.custom_build. 318c69351
OS/device including version: Arch Linux, 64 bit, updated around mid-December.
Issue description:
When connecting to an object from multiple threads, its slot_map
can get corrupted, leading to missing entries or crashes. I feel something similar might be possible with the connections
list
Issue was observed by Discord user Sungray while creating multiple sprite nodes with the same texture from different threads. (Which leads to those sprites connecting to the texture’s changed signal.)
Steps to reproduce: Put this script on any node:
extends Node
signal dummy_signal()
class DummyObject extends Object:
func dummy_method():
pass
func _ready():
var threads = []
for i in range(2):
var thread = Thread.new()
thread.start(self, "_thread", null)
threads.push_back(thread)
func _thread(_x):
while true:
var object = DummyObject.new()
connect("dummy_signal", object, "dummy_method") # dummy method
object.call_deferred("free") # Run the free on the main thread, locks up
#object.free() # Run the free on the non-main thread, crashes
Run, should crash or enter an infinite loop on the main thread relatively fast. When the project freezes, it should spam a errors looking like:
ERROR: _disconnect: Disconnecting nonexistent signal 'dummy_signal', slot: 1224:dummy_method.
At: core/object.cpp:1526.
When crashing, it can generate a wide array of backtraces. Some of them are related to malloc
or free
, surprisingly.
No backtrace crashes (likely malloc-related)
handle_crash: Program crashed with signal 11
Dumping the backtrace. Please include this when reporting the bug on https://github.com/godotengine/godot/issues
malloc(): invalid next->prev_inuse (unsorted)
realloc(): invalid next size
double free or corruption (!prev)
free(): corrupted unsorted chunks
One or more backtrace crashes
handle_crash: Program crashed with signal 4
Dumping the backtrace. Please include this when reporting the bug on https://github.com/godotengine/godot/issues
handle_crash: Program crashed with signal 11
handle_crash: Program crashed with signal 11
Dumping the backtrace. Please include this when reporting the bug on https://github.com/godotengine/godot/issues
Dumping the backtrace. Please include this when reporting the bug on https://github.com/godotengine/godot/issues
[1] /usr/lib/libc.so.6(+0x3bfb0) [0x7fa7a7b21fb0] (??:0)
[1] /usr/lib/libc.so.6(+0x3bfb0) [0x7fa7a7b21fb0] (??:0)
[1] /usr/lib/libc.so.6(+0x3bfb0) [0x7fa7a7b21fb0] (??:0)
[2] Object::Signal::Target::operator<(Object::Signal::Target const&) const (/home/bobo/Projects/godot/godot/./core/object.h:449)
[2] StringName::unref() (/home/bobo/Projects/godot/godot/./core/safe_refcount.h:118)
[2] CowData<VMap<Object::Signal::Target, Object::Signal::Slot>::Pair>::get_m(int) (/home/bobo/Projects/godot/godot/./core/cowdata.h:145)
[3] StringName::~StringName() (/home/bobo/Projects/godot/godot/core/string_name.cpp:422)
[3] VMap<Object::Signal::Target, Object::Signal::Slot>::_find_exact(Object::Signal::Target const&) const (/home/bobo/Projects/godot/godot/./core/vmap.h:104)
[3] VMap<Object::Signal::Target, Object::Signal::Slot>::operator[](Object::Signal::Target const&) (/home/bobo/Projects/godot/godot/./core/vmap.h:202)
[4] Object::Connection::~Connection() (/home/bobo/Projects/godot/godot/./core/object.h:412)
[4] VMap<Object::Signal::Target, Object::Signal::Slot>::has(Object::Signal::Target const&) const (/home/bobo/Projects/godot/godot/./core/vmap.h:131)
[4] Object::connect(StringName const&, Object*, StringName const&, Vector<Variant> const&, unsigned int) (/home/bobo/Projects/godot/godot/core/object.cpp:1485)
[5] Object::Signal::Slot::~Slot() (/home/bobo/Projects/godot/godot/./core/object.h:458)
[5] MethodBind5R<Object, Error, StringName const&, Object*, StringName const&, Vector<Variant> const&, unsigned int>::call(Object*, Variant const**, int, Variant::CallError&) (/home/bobo/Projects/godot/godot/core/method_bind.gen.inc:4285)
[5] Object::_disconnect(StringName const&, Object*, StringName const&, bool) (/home/bobo/Projects/godot/godot/core/object.cpp:1526)
[6] VMap<Object::Signal::Target, Object::Signal::Slot>::Pair::~Pair() (/home/bobo/Projects/godot/godot/./core/vmap.h:40)
[6] Object::~Object() (/home/bobo/Projects/godot/godot/core/object.cpp:1974)
[6] Object::call(StringName const&, Variant const**, int, Variant::CallError&) (/home/bobo/Projects/godot/godot/core/object.cpp:921)
[7] void memdelete<Object>(Object*) (/home/bobo/Projects/godot/godot/./core/os/memory.h:117)
[7] CowData<VMap<Object::Signal::Target, Object::Signal::Slot>::Pair>::resize(int) (/home/bobo/Projects/godot/godot/./core/cowdata.h:304)
[7] Variant::call_ptr(StringName const&, Variant const**, int, Variant*, Variant::CallError&) (/home/bobo/Projects/godot/godot/core/variant_call.cpp:1112)
[8] Object::call(StringName const&, Variant const**, int, Variant::CallError&) (/home/bobo/Projects/godot/godot/core/object.cpp:893)
[8] CowData<VMap<Object::Signal::Target, Object::Signal::Slot>::Pair>::insert(int, VMap<Object::Signal::Target, Object::Signal::Slot>::Pair const&) (/home/bobo/Projects/godot/godot/./core/cowdata.h:175)
[8] GDScriptFunction::call(GDScriptInstance*, Variant const**, int, Variant::CallError&, GDScriptFunction::CallState*) (/home/bobo/Projects/godot/godot/modules/gdscript/gdscript_function.cpp:1081)
[9] GDScriptInstance::call(StringName const&, Variant const**, int, Variant::CallError&) (/home/bobo/Projects/godot/godot/modules/gdscript/gdscript.cpp:1173)
[9] MessageQueue::_call_function(Object*, StringName const&, Variant const*, int, bool) (/home/bobo/Projects/godot/godot/core/message_queue.cpp:250)
[9] VMap<Object::Signal::Target, Object::Signal::Slot>::insert(Object::Signal::Target const&, Object::Signal::Slot const&) (/home/bobo/Projects/godot/godot/./core/vmap.h:125)
[10] Object::call(StringName const&, Variant const**, int, Variant::CallError&) (/home/bobo/Projects/godot/godot/core/object.cpp:900)
[10] MessageQueue::flush() (/home/bobo/Projects/godot/godot/core/message_queue.cpp:299)
[10] VMap<Object::Signal::Target, Object::Signal::Slot>::operator[](Object::Signal::Target const&) (/home/bobo/Projects/godot/godot/./core/vmap.h:199)
[11] SceneTree::iteration(float) (/home/bobo/Projects/godot/godot/scene/main/scene_tree.cpp:485)
[11] _Thread::_start_func(void*) (/home/bobo/Projects/godot/godot/core/bind/core_bind.cpp:2660)
[11] Object::connect(StringName const&, Object*, StringName const&, Vector<Variant> const&, unsigned int) (/home/bobo/Projects/godot/godot/core/object.cpp:1485)
[12] Main::iteration() (/home/bobo/Projects/godot/godot/main/main.cpp:1987)
[13] OS_X11::run() (/home/bobo/Projects/godot/godot/platform/x11/os_x11.cpp:3255)
[12] ThreadPosix::thread_callback(void*) (/home/bobo/Projects/godot/godot/drivers/unix/thread_posix.cpp:76)
[14] godot3.dbg(main+0x1a3) [0x178b5f3] (/home/bobo/Projects/godot/godot/platform/x11/godot_x11.cpp:56)
[13] /usr/lib/libpthread.so.0(+0x94cf) [0x7fa7a80074cf] (??:0)
[15] /usr/lib/libc.so.6(__libc_start_main+0xf3) [0x7fa7a7b0d153] (??:0)
[14] /usr/lib/libc.so.6(clone+0x43) [0x7fa7a7be52d3] (??:0)
-- END OF BACKTRACE --
handle_crash: Program crashed with signal 11
Dumping the backtrace. Please include this when reporting the bug on https://github.com/godotengine/godot/issues
[2] CowData<VMap<Object::Signal::Target, Object::Signal::Slot>::Pair>::get_m(int) (/home/bobo/Projects/godot/godot/./core/cowdata.h:145)
[1] /usr/lib/libc.so.6(+0x3bfb0) [0x7f2df29e5fb0] (??:0)
Failed method: Object:free target ID: 44863
Object was deleted while awaiting a callback # Repeated many times
## This one is fishy -- probably one of the threads crashed, while the other continued working and spamming the message queue.
Without deferring free
:
handle_crash: Program crashed with signal 11
Dumping the backtrace. Please include this when reporting the bug on https://github.com/godotengine/godot/issues
[1] /usr/lib/libc.so.6(+0x3bfb0) [0x7f523442dfb0] (??:0)
[2] Object::Signal::Target::operator<(Object::Signal::Target const&) const (/home/bobo/Projects/godot/godot/./core/object.h:449)
[3] VMap<Object::Signal::Target, Object::Signal::Slot>::_find_exact(Object::Signal::Target const&) const (/home/bobo/Projects/godot/godot/./core/vmap.h:104)
[4] VMap<Object::Signal::Target, Object::Signal::Slot>::has(Object::Signal::Target const&) const (/home/bobo/Projects/godot/godot/./core/vmap.h:131)
[5] Object::_disconnect(StringName const&, Object*, StringName const&, bool) (/home/bobo/Projects/godot/godot/core/object.cpp:1526)
[6] Object::~Object() (/home/bobo/Projects/godot/godot/core/object.cpp:1974)
[7] void memdelete<Object>(Object*) (/home/bobo/Projects/godot/godot/./core/os/memory.h:117)
[8] Object::call(StringName const&, Variant const**, int, Variant::CallError&) (/home/bobo/Projects/godot/godot/core/object.cpp:893)
[9] Variant::call_ptr(StringName const&, Variant const**, int, Variant*, Variant::CallError&) (/home/bobo/Projects/godot/godot/core/variant_call.cpp:1112)
[10] GDScriptFunction::call(GDScriptInstance*, Variant const**, int, Variant::CallError&, GDScriptFunction::CallState*) (/home/bobo/Projects/godot/godot/modules/gdscript/gdscript_function.cpp:1081)
[11] GDScriptInstance::call(StringName const&, Variant const**, int, Variant::CallError&) (/home/bobo/Projects/godot/godot/modules/gdscript/gdscript.cpp:1173)
[12] Object::call(StringName const&, Variant const**, int, Variant::CallError&) (/home/bobo/Projects/godot/godot/core/object.cpp:900)
[13] _Thread::_start_func(void*) (/home/bobo/Projects/godot/godot/core/bind/core_bind.cpp:2660)
[14] ThreadPosix::thread_callback(void*) (/home/bobo/Projects/godot/godot/drivers/unix/thread_posix.cpp:76)
[15] /usr/lib/libpthread.so.0(+0x94cf) [0x7f52349134cf] (??:0)
[16] /usr/lib/libc.so.6(clone+0x43) [0x7f52344f12d3] (??:0)
-- END OF BACKTRACE --
ERROR: _find: low > high, this may be a bug
At: ./core/vmap.h:70.
handle_crash: Program crashed with signal 11
Dumping the backtrace. Please include this when reporting the bug on https://github.com/godotengine/godot/issues
[1] /usr/lib/libc.so.6(+0x3bfb0) [0x7f9a19eadfb0] (??:0)
[2] StringName::operator=(StringName const&) (/home/bobo/Projects/godot/godot/./core/safe_refcount.h:107)
[3] Object::Connection::operator=(Object::Connection const&) (/home/bobo/Projects/godot/godot/./core/object.h:412)
[4] Object::Signal::Slot::operator=(Object::Signal::Slot const&) (/home/bobo/Projects/godot/godot/./core/object.h:458)
[5] VMap<Object::Signal::Target, Object::Signal::Slot>::Pair::operator=(VMap<Object::Signal::Target, Object::Signal::Slot>::Pair const&) (/home/bobo/Projects/godot/godot/./core/vmap.h:40)
[6] CowData<VMap<Object::Signal::Target, Object::Signal::Slot>::Pair>::remove(int) (/home/bobo/Projects/godot/godot/./core/cowdata.h:164)
[7] VMap<Object::Signal::Target, Object::Signal::Slot>::erase(Object::Signal::Target const&) (/home/bobo/Projects/godot/godot/./core/vmap.h:140)
[8] Object::_disconnect(StringName const&, Object*, StringName const&, bool) (/home/bobo/Projects/godot/godot/core/object.cpp:1538)
[9] Object::~Object() (/home/bobo/Projects/godot/godot/core/object.cpp:1974)
[10] void memdelete<Object>(Object*) (/home/bobo/Projects/godot/godot/./core/os/memory.h:117)
[11] Object::call(StringName const&, Variant const**, int, Variant::CallError&) (/home/bobo/Projects/godot/godot/core/object.cpp:893)
[12] Variant::call_ptr(StringName const&, Variant const**, int, Variant*, Variant::CallError&) (/home/bobo/Projects/godot/godot/core/variant_call.cpp:1112)
[13] GDScriptFunction::call(GDScriptInstance*, Variant const**, int, Variant::CallError&, GDScriptFunction::CallState*) (/home/bobo/Projects/godot/godot/modules/gdscript/gdscript_function.cpp:1081)
[14] GDScriptInstance::call(StringName const&, Variant const**, int, Variant::CallError&) (/home/bobo/Projects/godot/godot/modules/gdscript/gdscript.cpp:1173)
[15] Object::call(StringName const&, Variant const**, int, Variant::CallError&) (/home/bobo/Projects/godot/godot/core/object.cpp:900)
[16] _Thread::_start_func(void*) (/home/bobo/Projects/godot/godot/core/bind/core_bind.cpp:2660)
[17] ThreadPosix::thread_callback(void*) (/home/bobo/Projects/godot/godot/drivers/unix/thread_posix.cpp:76)
[18] /usr/lib/libpthread.so.0(+0x94cf) [0x7f9a1a3934cf] (??:0)
[19] /usr/lib/libc.so.6(clone+0x43) [0x7f9a19f712d3] (??:0)
-- END OF BACKTRACE --
(gdb) bt
#0 0x00000000046840c8 in atomic_conditional_increment<unsigned int> (pw=0x20051) at ./core/safe_refcount.h:107
#1 SafeRefCount::ref (this=0x20051) at ./core/safe_refcount.h:182
#2 StringName::StringName (this=0x7fffcc001158, p_name=...) at core/string_name.cpp:173
#3 0x0000000002d71d6b in Object::Connection::Connection (this=0x7fffcc001140) at ./core/object.h:412
#4 0x000000000462541d in Object::Signal::Slot::Slot (this=0x7fffcc001138) at ./core/object.h:458
#5 0x000000000462536f in VMap<Object::Signal::Target, Object::Signal::Slot>::Pair::Pair (this=0x7fffcc001128) at ./core/vmap.h:40
#6 0x0000000004625279 in CowData<VMap<Object::Signal::Target, Object::Signal::Slot>::Pair>::_copy_on_write (this=0x7fffcc000f60)
at ./core/cowdata.h:241
#7 0x00000000046261de in CowData<VMap<Object::Signal::Target, Object::Signal::Slot>::Pair>::set (this=0x7fffcc000f60, p_index=1,
p_elem=...) at ./core/cowdata.h:139
#8 0x0000000004625bb5 in CowData<VMap<Object::Signal::Target, Object::Signal::Slot>::Pair>::insert (this=0x7fffcc000f60, p_pos=1,
p_val=...) at ./core/cowdata.h:178
#9 0x000000000462585d in VMap<Object::Signal::Target, Object::Signal::Slot>::insert (this=0x7fffcc000f60, p_key=..., p_val=...)
at ./core/vmap.h:125
#10 0x00000000046220e6 in VMap<Object::Signal::Target, Object::Signal::Slot>::operator[] (this=0x7fffcc000f60, p_key=...)
at ./core/vmap.h:199
#11 0x000000000461a406 in Object::connect (this=0x6f9f170, p_signal=..., p_to_object=0x7fffcc000b90, p_to_method=..., p_binds=...,
p_flags=0) at core/object.cpp:1485
#12 0x0000000004630e8f in MethodBind5R<Object, Error, StringName const&, Object*, StringName const&, Vector<Variant> const&, unsigned int>::call (this=0x6409750, p_object=0x6f9f170, p_args=0x7fffd7eb8ef0, p_arg_count=3, r_error=...) at core/method_bind.gen.inc:4285
#13 0x00000000046157f9 in Object::call (this=0x6f9f170, p_method=..., p_args=0x7fffd7eb8ef0, p_argcount=3, r_error=...)
at core/object.cpp:921
#14 0x00000000046d28f5 in Variant::call_ptr (this=0x7fffd7ebaa60, p_method=..., p_args=0x7fffd7eb8ef0, p_argcount=3, r_ret=0x0,
r_error=...) at core/variant_call.cpp:1112
#15 0x00000000018ea067 in GDScriptFunction::call (this=0x67f0d70, p_instance=0x7260fa0, p_args=0x7fffd7ebad60, p_argcount=1,
r_err=..., p_state=0x0) at modules/gdscript/gdscript_function.cpp:1081
#16 0x0000000001883b04 in GDScriptInstance::call (this=0x7260fa0, p_method=..., p_args=0x7fffd7ebad60, p_argcount=1, r_error=...)
at modules/gdscript/gdscript.cpp:1173
#17 0x000000000461563f in Object::call (this=0x6f9f170, p_method=..., p_args=0x7fffd7ebad60, p_argcount=1, r_error=...)
at core/object.cpp:900
#18 0x000000000492bc25 in _Thread::_start_func (ud=0x70e3f00) at core/bind/core_bind.cpp:2660
#19 0x000000000242b95c in ThreadPosix::thread_callback (userdata=0x725f4f0) at drivers/unix/thread_posix.cpp:74
#20 0x00007ffff7a164cf in start_thread () from /usr/lib/libpthread.so.0
#21 0x00007ffff75f42d3 in clone () from /usr/lib/libc.so.6
(gdb) bt
#0 0x000000000462513a in CowData<VMap<Object::Signal::Target, Object::Signal::Slot>::Pair>::get_m (this=0x7fffcc000f60, p_index=1)
at ./core/cowdata.h:145
#1 0x0000000004622129 in VMap<Object::Signal::Target, Object::Signal::Slot>::operator[] (this=0x7fffcc000f60, p_key=...)
at ./core/vmap.h:202
#2 0x000000000461a406 in Object::connect (this=0x6f9e950, p_signal=..., p_to_object=0x7fffcc000b90, p_to_method=..., p_binds=...,
p_flags=0) at core/object.cpp:1485
#3 0x0000000004630e8f in MethodBind5R<Object, Error, StringName const&, Object*, StringName const&, Vector<Variant> const&, unsigned int>::call (this=0x6409750, p_object=0x6f9e950, p_args=0x7fffd7eb8ef0, p_arg_count=3, r_error=...) at core/method_bind.gen.inc:4285
#4 0x00000000046157f9 in Object::call (this=0x6f9e950, p_method=..., p_args=0x7fffd7eb8ef0, p_argcount=3, r_error=...)
at core/object.cpp:921
#5 0x00000000046d28f5 in Variant::call_ptr (this=0x7fffd7ebaa60, p_method=..., p_args=0x7fffd7eb8ef0, p_argcount=3, r_ret=0x0,
r_error=...) at core/variant_call.cpp:1112
#6 0x00000000018ea067 in GDScriptFunction::call (this=0x67f0560, p_instance=0x7260480, p_args=0x7fffd7ebad60, p_argcount=1,
r_err=..., p_state=0x0) at modules/gdscript/gdscript_function.cpp:1081
#7 0x0000000001883b04 in GDScriptInstance::call (this=0x7260480, p_method=..., p_args=0x7fffd7ebad60, p_argcount=1, r_error=...)
at modules/gdscript/gdscript.cpp:1173
#8 0x000000000461563f in Object::call (this=0x6f9e950, p_method=..., p_args=0x7fffd7ebad60, p_argcount=1, r_error=...)
at core/object.cpp:900
#9 0x000000000492bc25 in _Thread::_start_func (ud=0x70e3610) at core/bind/core_bind.cpp:2660
#10 0x000000000242b95c in ThreadPosix::thread_callback (userdata=0x725e9d0) at drivers/unix/thread_posix.cpp:74
#11 0x00007ffff7a164cf in start_thread () from /usr/lib/libpthread.so.0
#12 0x00007ffff75f42d3 in clone () from /usr/lib/libc.so.6
Minimal reproduction project:
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 9
- Comments: 21 (13 by maintainers)
This issue is rather problematic. This basically means that I cannot use a Thread to instance nodes that will eventually have to get freed again as there’s always the chance of this signal crash when I free nodes that were instantiated from a thread. And instancing all my nodes from the main thread is not an option as it causes stuttering in my project.
I will continue to try and find workarounds, are there any already known workarounds for this crash? A way to free nodes that were instantiated from a separate thread instance without it potentially crashing the game?
The issue i was having was not created by queue_free in a thread. the signal map becomes corrupted when you call .instance() from the thread because the thread manipulates the signal map without locking it. It shows up later when you free the instance but the actual corruption happens when you create the nodes from a separate thread (in my case)
Some issues with this situation that aren’t easily manageable by explicit thread safe methods, or at all:
AudioStreamPlayer
) connect signals to servers unconditionally, on creation #77518Simple actions such as attaching a resource to a node, while doing the correct thing of preparing it on a separate thread, not realizing the resource was cached and is not unique, and simply setting the mesh of a
MeshInstance
can cause this (see #60098), and I don’t necessarily think it’s reasonable to entirely forgo using cached resources for this, nor really to make convoluted code to replace the resource in the main threadEssentially, there are a bunch of cases where signals are connected, unbeknownst to the user, and trying to navigate the thread safety of that is hard even for experienced users, and the behaviour in this case isn’t something covered by the stability or compatibility of the engine, so a function can suddenly start connecting a signal that it didn’t in the last patch release, throwing everything off
To me this level of unpredictability really warrants making signal connections thread safe, while I do get that explicitly connecting signals should be something the user needs to be aware of isn’t guaranteed to be thread safe, this isn’t necessarily limited to that
A possible (at least temporary) fix would be to provide a function to make the connection in a deferred way, possibly making this thread-aware and connecting directly if on the main thread (I’m realising with this that a general option to do deferred calls only if not on the main thread as a special function might be an interesting idea)
This looks promising: https://www.stroustrup.com/lock-free-vector.pdf Or this: https://github.com/cameron314/concurrentqueue https://www.1024cores.net/home/lock-free-algorithms/queues
Cas operations are slower than no thread safety… but how much slower when contention is rare?
Alternative ideas:
https://www.1024cores.net/home/lock-free-algorithms/queues/intrusive-mpsc-node-based-queue
These are all very complicated and usually focused more on hpc and high degrees of parallelism… maybe something could be done with a fast path/slow path where the slow path syncs access to a journal with a mutex and the fast path (when the journal is empty) only needs a memory barrier… the journal can be replayed on thread access and committed on the main thread access…
Not a lot of things in Godot are thread-safe (https://docs.godotengine.org/en/stable/tutorials/threads/thread_safe_apis.html).
Object
is not designed to be. among them, for performance reasons, since that would meanObject
’s code would be full of mutexes when the most relevant use case is single threaded access.In order to manipulate most types of
Object
s from multiple threads, a project needs to use its own locking mechanism (based onMutex
,Sempahore
, etc.). The case of connecting signals from arbitrary threads is a tricky one, since the engine will check the connection map at some point without respecting any sync mechanism the project is using.So, for anything shared with the engine in such a way that it may access it at hard to predict moments (like signal connections, nodes already in the scene tree, etc.), the right way to operate on it in a multiple threads context (if you really need to) is to create some sort of communicaiton channel between your arbitrary thread and the main one, and to let the latter do what is necessary.
This is a way to approach the example from the OP safely:
UPDATE: Now I’m realizing that the deferred call to
do_the_connection()
can potentially thrash the engine’s message queue, too. However, I don’t have more time to spend on this example and I guess it already illustrates my point. In any case, if threads are not as aggressive as in this example, that wouldn’t be necessary; and the deferred deleter probably either (call_deferred('free')
would be just fine).That’s pretty much @RandomShaper’s de facto area of expertise now 😃
@Flarkk Unfortunately not! I had this solution where I was constantly instancing scenes from a thread and queue freeing those scenes from the main thread again. Hundreds of times per second. In this case, this issue would occur but rarely. Now I am no longer freeing scenes and I am instead reusing them so I no longer run into this potential crash anymore.
Short messages like
malloc(): invalid next->prev_inuse (unsorted)
orrealloc(): invalid next size
means that memory is too damaged to continue the program and the program can crash in random moments(each time may be different backtrace) I found that using Address Sanitizerscons p=x11 -j6 use_asan=yes
show a lot of more detailed backtrace of first use invalid memory, double free etc. instead show backtrace of crash, which can happens a lot of time later. This is output from Asan(This time is very similar to crash backtraces)