Skip to content

Fix stale state in callbacks when multiple events fire rapidly#3988

Merged
Madoshakalaka merged 1 commit intomasterfrom
fix/stale-state-issue-3796-v2
Feb 16, 2026
Merged

Fix stale state in callbacks when multiple events fire rapidly#3988
Madoshakalaka merged 1 commit intomasterfrom
fix/stale-state-issue-3796-v2

Conversation

@Madoshakalaka
Copy link
Member

Description

UseReducerHandle previously cached a single Rc snapshot at render time. When dispatch updated the shared RefCell, callback closures still held handles pointing to the old snapshot, causing stale reads.

Replace the snapshot with a deref_history approach: Deref now clones the latest Rc from the shared RefCell and accumulates it in a Vec so that returned references remain valid for the handle's lifetime. The history is deduplicated by Rc::ptr_eq to avoid growth from repeated reads and is reset on each re-render when a new handle is created.

This avoids the unsound pattern from PR #3963, where the Ref guard from try_borrow() was dropped while a pointer derived from it was returned. That caused use-after-free when dispatch replaced the Rc (refcount 1) and the allocator reused the freed memory. A dedicated test exercises this exact sequence and confirms it no longer corrupts memory.

Fixes #3796
Supersedes #3963

Fixes #0000

Checklist

  • I have reviewed my own code
  • I have added tests

(a test for the issue, and a test the old PR #3963 fails on)

UseReducerHandle previously cached a single Rc<T> snapshot at render
time. When dispatch updated the shared RefCell, callback closures still
held handles pointing to the old snapshot, causing stale reads.

Replace the snapshot with a deref_history approach: Deref now clones the
latest Rc<T> from the shared RefCell and accumulates it in a Vec so that
returned references remain valid for the handle's lifetime. The history
is deduplicated by Rc::ptr_eq to avoid growth from repeated reads and
is reset on each re-render when a new handle is created.

This avoids the unsound pattern from PR #3963, where the Ref guard from
try_borrow() was dropped while a pointer derived from it was returned.
That caused use-after-free when dispatch replaced the Rc (refcount 1)
and the allocator reused the freed memory. A dedicated test exercises
this exact sequence and confirms it no longer corrupts memory.

Fixes #3796
Supersedes #3963
@github-actions
Copy link

github-actions bot commented Feb 16, 2026

Visit the preview URL for this PR (updated for commit 0eda3a8):

https://yew-rs-api--pr3988-fix-stale-state-issu-0xcma3oj.web.app

(expires Mon, 23 Feb 2026 06:10:35 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

@github-actions
Copy link

github-actions bot commented Feb 16, 2026

Benchmark - core

Yew Master

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.746 ns      │ 4.291 ns      │ 2.751 ns      │ 2.815 ns      │ 100     │ 1000000000

Pull Request

vnode           fastest       │ slowest       │ median        │ mean          │ samples │ iters
╰─ vnode_clone  2.714 ns      │ 4.043 ns      │ 2.723 ns      │ 3.158 ns      │ 100     │ 1000000000

@github-actions
Copy link

github-actions bot commented Feb 16, 2026

Benchmark - SSR

Yew Master

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 291.199 291.542 291.369 0.120
Hello World 10 531.964 542.242 535.735 3.466
Function Router 10 1689.703 1712.502 1698.590 6.411
Concurrent Task 10 1005.420 1006.934 1006.034 0.603
Many Providers 10 1055.775 1138.423 1094.901 28.582

Pull Request

Details
Benchmark Round Min (ms) Max (ms) Mean (ms) Standard Deviation
Baseline 10 291.161 294.378 291.672 0.961
Hello World 10 487.243 499.536 492.468 4.026
Function Router 10 1672.792 1687.640 1679.437 5.691
Concurrent Task 10 1005.325 1007.112 1006.394 0.560
Many Providers 10 1098.651 1128.526 1112.744 8.440

@github-actions
Copy link

github-actions bot commented Feb 16, 2026

Size Comparison

Details
examples master (KB) pull request (KB) diff (KB) diff (%)
async_clock 96.753 96.753 0 0.000%
boids 165.414 165.414 0 0.000%
communication_child_to_parent 89.911 89.911 0 0.000%
communication_grandchild_with_grandparent 100.955 100.955 0 0.000%
communication_grandparent_to_grandchild 97.457 97.457 0 0.000%
communication_parent_to_child 87.330 87.330 0 0.000%
contexts 102.046 102.952 +0.906 +0.888%
counter 84.052 84.052 0 0.000%
counter_functional 84.556 85.426 +0.870 +1.029%
dyn_create_destroy_apps 87.057 87.057 0 0.000%
file_upload 96.576 96.576 0 0.000%
function_delayed_input 90.108 91.006 +0.897 +0.996%
function_memory_game 166.986 168.944 +1.958 +1.173%
function_router 322.524 326.408 +3.884 +1.204%
function_todomvc 159.060 160.918 +1.858 +1.168%
futures 232.386 232.386 0 0.000%
game_of_life 102.229 102.229 0 0.000%
immutable 243.259 244.674 +1.415 +0.582%
inner_html 78.631 78.631 0 0.000%
js_callback 105.978 107.498 +1.521 +1.435%
keyed_list 177.176 177.176 0 0.000%
mount_point 81.767 81.767 0 0.000%
nested_list 110.766 110.766 0 0.000%
node_refs 89.222 89.222 0 0.000%
password_strength 1726.342 1726.342 0 0.000%
portals 90.788 90.788 0 0.000%
router 296.507 297.152 +0.646 +0.218%
suspense 109.847 110.996 +1.149 +1.046%
timer 86.562 86.562 0 0.000%
timer_functional 94.554 96.119 +1.565 +1.656%
todomvc 139.712 139.712 0 0.000%
two_apps 83.918 83.918 0 0.000%
web_worker_fib 131.891 133.679 +1.788 +1.356%
web_worker_prime 182.873 184.597 +1.724 +0.943%
webgl 81.185 81.185 0 0.000%

⚠️ The following examples have changed their size significantly:

examples master (KB) pull request (KB) diff (KB) diff (%)
counter_functional 84.556 85.426 +0.870 +1.029%
function_memory_game 166.986 168.944 +1.958 +1.173%
function_router 322.524 326.408 +3.884 +1.204%
function_todomvc 159.060 160.918 +1.858 +1.168%
js_callback 105.978 107.498 +1.521 +1.435%
suspense 109.847 110.996 +1.149 +1.046%
timer_functional 94.554 96.119 +1.565 +1.656%
web_worker_fib 131.891 133.679 +1.788 +1.356%

@Madoshakalaka Madoshakalaka force-pushed the fix/stale-state-issue-3796-v2 branch from ee9cd41 to 0eda3a8 Compare February 16, 2026 06:09
@Madoshakalaka Madoshakalaka marked this pull request as ready for review February 16, 2026 06:18
@Madoshakalaka Madoshakalaka merged commit 1ef5f54 into master Feb 16, 2026
26 checks passed
@Madoshakalaka Madoshakalaka added bug A-yew Area: The main yew crate labels Feb 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-yew Area: The main yew crate bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

State management and event handling interact badly leading to stale state

1 participant