Description
Godot version
v3.5.stable.official [991bb6a]
System information
Fedora 31
Issue description
Consider the following code.
extends Node2D
class A:
signal signal_a
class B:
signal signal_b
var a
func _init(a_):
a = a_
a.connect("signal_a", self, "_on_signal_a", [], CONNECT_ONESHOT)
func _on_signal_a():
emit_signal("signal_b")
func example_runner(b):
yield(b, "signal_b")
print("End of example_runner")
func _ready():
var a = A.new()
example_runner(B.new(a))
yield(get_tree(), "idle_frame")
a.emit_signal("signal_a")
A
is a simple object that emits a signal. B
contains an A
and forwards the signal_a
into its own signal_b
. Crucially, it does this only once (CONNECT_ONESHOT
is set on the signal connection). Both are reference types, not nodes.
Then we create an A
and a B
. We keep a reference to the A
in _ready
(basically, for the duration of this test, but the only living reference to the B
is in example_runner
. example_runner
yields to signal_b
and then prints a simple message to the screen. This is good enough to keep a reference to our B
object alive, at least until signal_b
fires.
In _ready
, we wait a frame and then emit signal_a
. This causes our B
object (which is being kept alive by the example_runner
function state) to receive signal_a
and emit its own signal_b
. In turn, this causes example_runner
to finish executing (printing its string to the console) and terminate.
Expected behavior: I expect all of this to happen with no errors.
Actual behavior: The code still executes as expected, but the terminal shows an error that
0:00:00.483 _disconnect: Disconnecting nonexistent signal 'signal_a', slot: 0:_on_signal_a.
I suspect that the sequence of events is as follows.
- Line 25 runs.
signal_a
is emitted. - The
B
object (which currently has one reference to it) getssignal_a
. This calls_on_signal_a
. _on_signal_a
emitssignal_b
.example_runner
was waiting onsignal_b
, so it resumes. It prints something and finishes executing.example_runner
held the only reference to ourB
. So theB
object is freed at this time, by reference counting semantics.- The C++ code that disconnects one-shot signals runs, attempting to disconnect our signal. But the
B
object it was set on is already freed. Error!
The C++ code in Object::emit_signal
is keeping a reference to our target object B
, but it seems to not update the reference count, which allows the B
object to be freed while C++ still holds a reference to it and intends to do something with it.
Steps to reproduce
The above code snippet is available in an attached project below. Simply run the Main.tscn
scene. You will see an error appear in the console, as indicated above.