Skip to content

State.reset() should not reset inherited backend vars#6365

Merged
adhami3310 merged 7 commits into
mainfrom
masenf/shared-state-reset
Apr 23, 2026
Merged

State.reset() should not reset inherited backend vars#6365
adhami3310 merged 7 commits into
mainfrom
masenf/shared-state-reset

Conversation

@masenf
Copy link
Copy Markdown
Collaborator

@masenf masenf commented Apr 22, 2026

self.backend_vars contains backend vars from the current state instance and its parent states. When performing the reset, we should never touch vars that belong to the parent(s).

Ensure that a State instance's _backend_vars only copies backend vars that were defined in the given substate, and does not also copy inherited backend vars that are managed by the parent. Ensures that assigning an inherited backend var through a substate marks the owner as touched and not the substate it was assigned through.

masenf added 4 commits April 22, 2026 13:31
`self.backend_vars` contains backend vars from the current state instance and its
parent states. When performing the reset, we should never touch vars that
belong to the parent(s).
Ensure that a State instance's _backend_vars only copies backend vars that were
defined in the given substate, and does not also copy inherited backend vars
that are managed by the parent. Ensures that assigning an inherited backend var
through a substate marks the owner as touched and not the substate it was
assigned through.
`self._backend_vars` only contains vars owned by the given instance, so mark
these dirty instead of _all_ backend vars that are owned or inherited.
assert that the current instance's backend vars have been reset to
non-inherited backend vars.
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 22, 2026

Merging this PR will not alter performance

✅ 9 untouched benchmarks


Comparing masenf/shared-state-reset (fd3169e) with main (a4ebb0b)

Open in CodSpeed

assert that the newly reset values equal the default values for non-inherited vars
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR fixes a correctness bug where State.reset() on a child state would inadvertently reset backend vars that belong to parent states (via __setattr__ delegation), breaking SharedState event-link context. The fix narrows _backend_vars to only store vars owned by each state class, makes reset() skip inherited vars, and aligns _get_was_touched and _patch_state's full-delta path to use _backend_vars for ownership-accurate dirty tracking. Unit and integration tests cover the regression, the new ownership invariant, and the _was_touched propagation.

Confidence Score: 5/5

Safe to merge; the fix is well-scoped and all remaining findings are P2 (test robustness suggestion only).

The core logic change is correct and consistent across all affected paths (init, reset, _get_was_touched, _patch_state). Three unit tests directly verify the new invariants, and an integration test exercises the reported end-to-end scenario. The only finding is a P2 concern about poll ordering in the integration test.

tests/integration/test_linked_state.py — poll ordering after reset could pass on stale DOM content

Important Files Changed

Filename Overview
reflex/state.py Core fix: _backend_vars now stores only owned backend vars (not inherited), reset() skips inherited vars, and _get_was_touched correctly uses _backend_vars so inherited var changes don't incorrectly mark the child as touched.
reflex/istate/shared.py One-line fix: full_delta path now iterates over _backend_vars (instance-owned vars only) instead of backend_vars (class-level all-vars dict) when marking dirty, consistent with the new ownership model.
tests/units/test_state.py Three well-structured unit tests cover the regression (reset isolation), the new _backend_vars ownership invariant, and the _was_touched propagation when an inherited backend var is set through the child.
tests/integration/test_linked_state.py New integration test exercises the reported scenario end-to-end; minor concern around poll ordering after reset vs record clicks (see inline comment).
tests/integration/test_component_state.py Assertion updated to match new semantics: _backend_vars keys must equal backend vars minus inherited backend vars.

Reviews (1): Last reviewed commit: "tighter assertion in test_component_stat..." | Re-trigger Greptile

Comment thread tests/integration/test_linked_state.py
explicitly reset the value that we're checking to blank before asserting that
it is not blank to avoid a case where a bad value overwrites the checked value
_after_ it was checked.
adhami3310
adhami3310 previously approved these changes Apr 23, 2026
@adhami3310 adhami3310 dismissed their stale review April 23, 2026 00:08

it's failing the test it added :(

Internally, SharedStateBaseInternal keeps track of which shared lock tokens it
is currently holding. If the user requests patching in a SharedState, but
doesn't currently hold the lock, then it will request the lock for the linked
token, store it in `_held_locks`, then load the state and patch it into the
tree. This is problematic when async computed vars are requesting the same
SharedState instance, because resolving each async var value runs in its own
task, so each var function races with the others to complete. Since these all
depend on acquiring the same lock, without an asyncio.Lock protecting the
critical `_held_locks` structure, each var will attempt to acquire the redis
lock to the exclusion of the other var tasks.

With the new `_held_locks_lock`, the internal machinery serializes the
acquisition of the linked token lock so that subsequent access can properly
reuse a single acquired lock, rather than each trying to acquire it and hold it
for the duration of the event being processed.
@adhami3310 adhami3310 merged commit 360075e into main Apr 23, 2026
68 checks passed
@adhami3310 adhami3310 deleted the masenf/shared-state-reset branch April 23, 2026 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants