godot: Errors when firing a signal just after awaiting on that signal
Tested versions
4.2.stable
System information
MacOS, M2
Issue description
Errors are logged when firing a signal just after awaiting on that signal. It seems that the await object is not properly cleaned up and is still partly waiting on the signal.
extends Node2D
signal my_signal()
func _ready() -> void:
await my_signal
my_signal.emit()
func _process(_delta: float) -> void:
my_signal.emit()
set_process(false)
gives
E 0:00:00:0589 Node2D.gd:8 @ _ready(): Resumed function '_ready()' after await, but script is gone. At script: res://Node2D.gd:7
<Erreur C++> Method/function failed. Returning: Variant()
<Source C++> modules/gdscript/gdscript_function.cpp:197 @ resume()
<Pile des appels>Node2D.gd:8 @ _ready()
Node2D.gd:13 @ _process()
E 0:00:00:0589 Node2D.gd:13 @ _process(): Attempt to disconnect a nonexistent connection from 'Node2D:<Node2D#26793215177>'. Signal: 'my_signal', callable: 'GDScriptFunctionState::_signal_callback'.
<Erreur C++> Condition "!s->slot_map.has(*p_callable.get_base_comparator())" is true. Returning: false
<Source C++> core/object/object.cpp:1420 @ _disconnect()
<Pile des appels>Node2D.gd:13 @ _process()
Steps to reproduce
Run the project below or the code sample above.
Minimal reproduction project (MRP)
About this issue
- Original URL
- State: closed
- Created 4 months ago
- Comments: 15 (14 by maintainers)
The direct cause is that
ONE_SHOT
connections to a signal are disconnected after the connected method is called, not before. Meaning e.g. this results in an infinite loop:Related issue: #28857.
How does it affect
await
? When awaiting on a signalGDScriptFunctionState::_signal_callback
which callsGDScriptFunctionState::resume
isONE_SHOT
-connected to such awaited signal:https://github.com/godotengine/godot/blob/61282068f4d59cb48f35ad95391728c58d9008ab/modules/gdscript/gdscript_vm.cpp#L2306 https://github.com/godotengine/godot/blob/61282068f4d59cb48f35ad95391728c58d9008ab/modules/gdscript/gdscript_function.cpp#L137-L167
Meaning if the awaited signal is emitted again during such
resume()
then it results in callingresume()
again because theONE_SHOT
connection is not yet disconnected.Execution goes something like:
The in
GDScriptFunctionState::resume
error part: https://github.com/godotengine/godot/blob/61282068f4d59cb48f35ad95391728c58d9008ab/modules/gdscript/gdscript_function.cpp#L190-L212How disconnecting
ONE_SHOT
connections is done: https://github.com/godotengine/godot/blob/61282068f4d59cb48f35ad95391728c58d9008ab/core/object/object.cpp#L1121-L1175I’m absolutely against this, there’s nothing wrong with the recursive signals on its own. Sure an infinite recursion is unwanted/problematic but if it happens then that would be a user error (assuming no in-engine bugs). Just because recursion can be misued doesn’t mean it should be disallowed.
BTW it’s already allowed hence changing it I’d consider as a breaking change (as I don’t think it’s a bug / needs a bugfix).
And that’s also kinda a separate discussion, even possibly a material for a proposal.
Just for information, you can call an async function without waiting for it. So the following example is simpler since it doesn’t involve node processing:
@AThousandShips To clarify: I’ve only pointed out the direct cause of the reported issue for the current implementation.
Whether how
ONE_SHOT
connections are being disconnected is intentional or it is a bug itself I don’t know, hence why I’ve asked about this in the contributors chat (I could have mentioned my doubts in my previous comment).But regardless of the current
ONE_SHOT
disconnecting behavior, what’s reported here i.e.await
producing errors was already triaged as a bug by a GDScript team member (dalexeev) so please respect this more / be more thoughful about changing the labels in the future (I’ll leave fixing them to you).The behavior of
await
seems already correct / as expected, what’s reported here is some superfluous/unneeded errors.So:
ONE_SHOT
disconnecting behavior is a bug then it needs to be fixed. The issue withawait
erroring would then be automatically fixed as a result.ONE_SHOT
disconnecting behavior is not a bug thenawait
’s implementation needs to be tweaked to fix the reported issue accordingly.@BimDav Your original MRP is fine/sufficient on its own, no need for a “real world” example to classify some easily reproducible behavior as a bug / not a bug.
@kleonc Thanks for investigating! @AThousandShips it is still a bug, though, right? Why did you remove the
bug
tag?It’s just a minimal code example to reproduce the bug. The bug is not linked to this happening in
_ready
or not. The bug is also not linked to theset_process(false)
, which I added to remove confusion due to the signal being emitted each frame. This way, it is only emitted once.