You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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) gets signal_a. This calls _on_signal_a.
_on_signal_a emits signal_b.
example_runner was waiting on signal_b, so it resumes. It prints something and finishes executing.
example_runner held the only reference to our B. So the B 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.
@YuriSizov Yes, I have just tested this on Godot v4.0.beta1.official [20d6672] and the problem is reproducible there as well (after replacing idle_frame with process_frame). Error message:
E 0:00:00:0425 _ready: Cannot disconnect 'signal_a' from callable 'null::_on_signal_a': the callable object is null.
<C++ Error> Condition "!target_object" is true.
<C++ Source> core/object/object.cpp:1320 @ _disconnect()
<Stack Trace> Main.gd:25 @ _ready()
Godot version
v3.5.stable.official [991bb6a]
System information
Fedora 31
Issue description
Consider the following code.
A
is a simple object that emits a signal.B
contains anA
and forwards thesignal_a
into its ownsignal_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 aB
. We keep a reference to theA
in_ready
(basically, for the duration of this test, but the only living reference to theB
is inexample_runner
.example_runner
yields tosignal_b
and then prints a simple message to the screen. This is good enough to keep a reference to ourB
object alive, at least untilsignal_b
fires.In
_ready
, we wait a frame and then emitsignal_a
. This causes ourB
object (which is being kept alive by theexample_runner
function state) to receivesignal_a
and emit its ownsignal_b
. In turn, this causesexample_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
I suspect that the sequence of events is as follows.
signal_a
is emitted.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.B
object it was set on is already freed. Error!The C++ code in
Object::emit_signal
is keeping a reference to our target objectB
, but it seems to not update the reference count, which allows theB
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.Minimal reproduction project
Workspace - Referenced Oneshot Signal.zip
The text was updated successfully, but these errors were encountered: