fix: keep an optional event handler alive when a rerender adds it - #5894
Open
XiangpengHao wants to merge 1 commit into
Open
XiangpengHao wants to merge 1 commit into
XiangpengHao wants to merge 1 commit into
Conversation
When a memoized component's optional event handler goes from None to Some, memoize moved the new props' handler into the old props. That handler is owned by the new props, which are dropped after the diff, so the component kept a dangling handler and the next diff panicked in __point_to (or with "RefCell already borrowed" once the freed slot was reused). The old props now take their own reference to the new handler, owned by their owner, the way every other handler and signal field is updated in place.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When a memoized component's
Option<EventHandler>prop goes fromNonetoSome,memoizemoves the new props' handler into the old props (theelsebranch added in #5225). That handler is owned by the new props'Owner, and the new props are dropped right after the diff, so the component is left holding a freed handler. The next diff of that component panics inCallback::__point_to:or with
RefCell already borrowedwhen the freed slot has already been reused for the new handler.Ordinary code hits it: two same-shaped branches, one without the handler and one with it, which the differ updates in place.
We hit it in an app as a search form's Search button (no
onclick) turning into a Stop button (withonclick) while a search runs; the page froze on the next progress update.Fix
Every other handler and signal field is updated in place, so the old props only ever keep boxes they own. This does the same for an optional handler that is added: the
WithOwnerwrapper runs the innermemoizeunder its own owner, and the old props take a new reference to the new handler (Callback::__reference_in_current_owner, built onOwner::insert_reference) instead of moving in the new props' box.Tests
optional_event_handler_diff, theOption<Callback>is always set toSomeeven ifNoneis explicitly passed #5222 regression test, already diffedNone→Somebut only assertedis_some(). It now calls the copied callback, which panicked withDroppedbefore this change.optional_event_handler_added_on_rerender_survivesreproduces it through a component; it panicked before this change.dioxus-core,dioxus-core-macro,dioxus-signals,dioxus-hooks,dioxus-stores,dioxus-ssranddioxus-routertests pass.Notes
Some→None→Somerepeatedly keeps one reference per flip alive until the component unmounts. That matches how the last handler is already kept alive afterSome→None; releasing it earlier would break copies the component made of it, asTakesEventHandlerin the same test file does.