Repository navigation
Fix hydration override mutation dependents - #7572
Conversation
Preserve fields-only replay output while resolving dependents invalidated by real assignments. Commit uncached delivery records against the final delta and retain shared-state cleanup bookkeeping. Fixes #7571
[Medium impact] The change affects browser-storage hydration and the state updates sent during reconnects. Restore the required news fragment before merging; no new behavioral defect was established. Findings
SummaryRestores delivery of cached storage dependents during hydration while keeping one public
Previous threads: the single traversal addresses duplicate override effects and restored filtered fields. Reviews (4) · Last reviewed commit: "fix(state): share the dependent walk bet..." · Reviewed by Greptile |
Merging this PR will degrade performance by 7.78%2 warnings
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes |
Mark cached storage dependents for delivery without invalidating their snapshot caches, then use one public get_delta traversal. Preserve output filters and existing synchronous/after-super ordering semantics without repeating override side effects or merging separately filtered deltas.
…erride-dependents # Conflicts: # tests/units/reflex_base/event/processor/test_base_state_processor.py
There was a problem hiding this comment.
All reported issues were addressed across 5 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
…on replay Fold the delivery-only marking into _mark_dirty_computed_vars behind an invalidate flag, so the hydration replay reuses the one dependency walk instead of carrying a second copy of it. Move the shared-cleanup test next to the module it covers and assert that the override's correction took effect. Drop the news fragment: #7568 is unreleased, so this fix changes nothing for any released version. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014CNQyjqBL56phnEuga6U6E
Fixes #7571.
#7568 avoids recomputing cached vars for unchanged browser storage, but its fields-only replay also drops cached dependents invalidated by assignments inside a
get_delta()override.Mark cached storage dependents for delivery without invalidating their snapshot caches, then pass the storage values through one public
get_delta()traversal. Real assignments invalidate caches normally. Uncached values stay in the normal event delta, avoiding an extra evaluation during hydration.This retains output-only overrides, field/computed filters, shared-state cleanup, and the single cached evaluation for unchanged storage. The original two-pass implementation was replaced after review reproduced duplicate override side effects and reintroduced omitted fields; both now have regression tests. No intermediate-delta merge or deferred recording machinery remains.
Contract
This restores the pre-#7568 traversal/return semantics. An override assigning after
super().get_delta()must update any already-collected fields it wants changed in its returned delta. Synchronous values collected earlier in the traversal are not rewritten afterward. Async cached sibling dependents resolve after the traversal and observe the corrected storage value, as in the issue reproduction.Two broader cases remain outside this regression fix: assigning an unrelated field after
super().get_delta()can leave its already-visited/absent sibling dependent stale; explicitly injecting a placeholder for an unchanged uncached value can leave its old delivery record intact. Targeted reproductions of both fail on pre-#7568 base2fc4a9caa5ce125acd9ce1f2e13cf5910ca5f8d8as well (2 failed, 3 controls passed). This PR does not claim to repair those inherited behaviors.Verification
154 added cases pass, including:
The exact minimal reproduction in #7571 fails on merged main and passes with this revision. All 144 assignment cases plus the two review regressions match pre-#7568 observable behavior (146/146); cached-evaluation counts are checked separately because avoiding those extra evaluations is the intentional improvement. Existing cached-once and uncached-no-extra evaluation controls pass.
Full Python 3.14.8 unit-suite rerun against unmodified
7ccaa4e213fa1f67374002cb28709272866c3d53:Full Ruff lint/format checks and Pyright pass (0 errors, 0 warnings). Published tree matches the tested revision. No news fragment: #7568 has not shipped in any release, so there is no user-facing change to record (
skip-changelog).Redis coverage uses the repository's mocked fixture. No local browser end-to-end test was run. CI results should be checked on the latest head.
Update: merged with main
mainwas merged into the branch; the only conflict was intest_base_state_processor.py, where both sides appended tests, and both sets are kept. On the merged head the processor,istate, andtest_statesuites pass (850 tests), and Ruff and Pyright are clean. The two Greptile P1 findings on the latest revision (a dependent of a field assigned aftersuper().get_delta(); a placeholder injected for an unchanged uncached var) were reproduced and fail identically on the pre-#7568 base8b48dc01c, so they are inheritedget_deltaoverride behavior rather than regressions of this change; details are in the thread replies.Update: review round
The hydration replay now marks dependents through
_mark_dirty_computed_vars(..., invalidate=False)instead of a second copy of the dependency walk, so one walk serves both cache invalidation and delivery-only marking. Benchmarked against main: the repo'stest_state_deltabenchmarks and a microbenchmark of_mark_dirty_computed_varsshow no difference beyond run-to-run noise, with roughly 20 ns on the no-dependents path for the extra parameter and check (numbers in the review thread). The shared-cleanup test moved totests/units/test_state.pyand asserts the override's correction took effect. The news fragment is removed, as above.🤖 Generated with Claude Code
https://claude.ai/code/session_014CNQyjqBL56phnEuga6U6E