Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Disconnect one-shot signals before calling callbacks #89451

Merged
1 commit merged into from
Mar 24, 2024

Conversation

AThousandShips
Copy link
Member

@AThousandShips AThousandShips commented Mar 13, 2024

Edit: Removed the recursion fix and can add it back in separately if desired, I still think it's an improvement but it makes this easier to merge and cherry-pick, keeping things as simple as possible for this case

This solves the issue, though I'd say some of the handling of await needs to be improved, see for example, so some further fixing of that side is likely needed:


@AThousandShips AThousandShips added bug topic:core crash cherrypick:4.2 Considered for cherry-picking into a future 4.2.x release labels Mar 13, 2024
@AThousandShips AThousandShips added this to the 4.3 milestone Mar 13, 2024
@AThousandShips AThousandShips requested a review from a team as a code owner March 13, 2024 17:09
@AThousandShips AThousandShips requested a review from a team March 13, 2024 18:19
@AThousandShips AThousandShips changed the title [Core] Disconnect signals before calling handlers [Core] Disconnect signals before calling callbacks Mar 13, 2024
@kleonc
Copy link
Member

kleonc commented Mar 14, 2024

Removed the recursion fix

I'd say if anything it should be opened as a separate PR anyway, it seems a separate thing to the disconnecting change.


Also if this gets approved as us (just the disconnecting part) then I think we should be careful with cherry-picking it to 4.2, I'd say that's a change which is likely to introduce some regressions we haven't thought about.


I'm not sure if the change is safe. It's core-core so I'd say it needs to be assesed by reduz anyway, so let's wait for such assesment. 🙂

@kleonc kleonc requested a review from reduz March 14, 2024 13:05
@BimDav
Copy link
Contributor

BimDav commented Mar 14, 2024

Thanks for looking into this!

core/object/object.cpp Outdated Show resolved Hide resolved
Copy link
Member

@reduz reduz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some notes on making the implementation more efficient

core/object/object.cpp Outdated Show resolved Hide resolved
@AThousandShips
Copy link
Member Author

Will update tomorrow, thank you!

@AThousandShips AThousandShips force-pushed the emit_fix branch 2 times, most recently from b46309e to 018d792 Compare March 20, 2024 11:46
@AThousandShips
Copy link
Member Author

AThousandShips commented Mar 20, 2024

There, updated:

  • Now using alloca instead of a LocalVector
  • Not copying Connection as only the callable and flags parts are relevant, and the copy constructor for this type is actually very inefficient as it goes via a Dictionary to get there
  • Removed the disconnect data entirely and just using the copied connections data

Still disconnecting all signals before emitting, to handle this:

signal my_signal

func foo():
    print("Foo")
    my_signal.emit()

func bar():
    print("Bar")

func _ready():
    my_signal.connect(foo, CONNECT_ONE_SHOT)
    my_signal.connect(bar, CONNECT_ONE_SHOT)
    my_signal.emit()

Without disconnecting all signals at once before emitting this will fire bar twice, as well as complain about disconnecting a non-existing signal

This cleans up a bunch of inefficiencies in this code, like storing the name of the signal in the disconnect data (not needed, only used in a single signal) and copying the whole Connection data, which involves copying a whole Signal

uint32_t slot_count = 0;

for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
memnew_placement(&slot_callables[slot_count], Callable(slot_kv.value.conn.callable));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

memnew is required here to handle the uninitialized memory

Copy link
Member Author

@AThousandShips AThousandShips Mar 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotta fix memory management here

Edit: Resolved, had to destroy them properly

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a better way to handle the lifetime here do tell, I don't have an asan setup at the moment so can't test efficiently for leaks

if (!c.callable.is_valid()) {
for (uint32_t i = 0; i < slot_count; ++i) {
const Callable &callable = slot_callables[i];
const uint32_t &flags = slot_flags[i];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added these for convenience, I ended up changing all the code below anyway as c went away but still neater

@AThousandShips AThousandShips requested a review from reduz March 20, 2024 11:52
@AThousandShips
Copy link
Member Author

Gonna add a GDScript test for this case and will push after CI has finished

@kleonc
Copy link
Member

kleonc commented Mar 20, 2024

Gonna add a GDScript test for this case and will push after CI has finished

While at it probably good idea to also add some test specifically for the fixed await usage, something like in #89439 (comment).

@AThousandShips
Copy link
Member Author

Will test out writing one!

@AThousandShips
Copy link
Member Author

And done, that test will fail with errors on the unchanged branch, and behaves nicely now

Copy link
Member

@reduz reduz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fantastic, great work!

@AThousandShips
Copy link
Member Author

Thank you!

@AThousandShips AThousandShips changed the title [Core] Disconnect signals before calling callbacks [Core] Disconnect one-shot signals before calling callbacks Mar 20, 2024
This prevents infinite recursion with one-shot connections emitting
themselves
akien-mga added a commit that referenced this pull request Mar 24, 2024
[Core] Disconnect one-shot signals before calling callbacks
@akien-mga akien-mga closed this pull request by merging all changes into godotengine:master in 06abc86 Mar 24, 2024
@akien-mga
Copy link
Member

Thanks!

@AThousandShips AThousandShips deleted the emit_fix branch March 24, 2024 08:45
@AThousandShips
Copy link
Member Author

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug cherrypick:4.2 Considered for cherry-picking into a future 4.2.x release topic:core
Projects
None yet
5 participants