Skip deep type-validation on state var hot paths - #6738
Conversation
Greptile SummaryThis PR reduces type-validation work on hot state-var paths. The main changes are:
Confidence Score: 4/5This is close, but the computed-var cache behavior should be fixed before merging.
Files Needing Attention: packages/reflex-base/src/reflex_base/vars/base.py
|
| Filename | Overview |
|---|---|
| packages/reflex-base/src/reflex_base/utils/types.py | Adds mode-based validation depth that re-reads the raw environment value. |
| packages/reflex-base/src/reflex_base/vars/base.py | Moves computed-var return validation to recompute paths, but cache hits can miss dev diagnostics after a mode switch. |
| reflex/state.py | Uses the new validation depth for state assignment diagnostics. |
| tests/units/reflex_base/utils/test_types.py | Adds coverage for validation depth across prod and dev modes. |
| tests/units/reflex_base/vars/test_base.py | Adds computed-var validation tests for recompute and cache-hit behavior. |
| tests/units/test_state.py | Adds coverage that wrong-typed state assignment still logs in dev mode. |
Reviews (5): Last reviewed commit: "Honor runtime env mode changes in _valid..." | Re-trigger Greptile
Merging this PR will improve performance by ×2.5
Performance Changes
Tip Curious why this is faster? Comment Comparing Footnotes
|
| setattr(instance, self._last_updated_attr, datetime.datetime.now()) | ||
| value = getattr(instance, self._cache_attr) | ||
| self._check_deprecated_return_type(instance, value) | ||
| return value |
There was a problem hiding this comment.
if we return early, how does the computed value get cached?
f75dcde to
1b480cb
Compare
Assigning a state var and reading a computed var both ran _isinstance(value, type, nested=1), walking every element of list/dict values only to gate a diagnostic log. The computed var check also ran on every access, including cache hits. - Validate computed var return types only when the value is recomputed (sync and async), not on cache hits. - Validate one container level deep only in dev mode; production now checks just the outer type (the check never gates behavior, it only logs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
Address review feedback: instead of caching the first observed mode forever, re-read the raw REFLEX_ENV_MODE value on every call and cache the depth per raw value, so in-process mode changes take effect immediately at negligible hot-path cost. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
dea5c0c to
460c941
Compare
1b480cb to
1f6a4f4
Compare
| return value | ||
|
|
||
| return value | ||
| return getattr(instance, self._cache_attr) |
There was a problem hiding this comment.
Cache Hit Skips Recheck When a cached computed var is first evaluated in prod, nested return values are checked with the shallow depth and then stored. If the same process switches to dev mode without changing the var dependencies, this cache-hit path returns the stored value without calling
_check_deprecated_return_type. Dev-mode access can keep returning a nested wrong-typed value with no diagnostic until an unrelated recompute happens. The cache needs to account for validation depth changes, or mode changes need to invalidate these cached values.
|
Folded into #6743, which now spans the full state hot-path set — this PR, #6740, and the dirty-propagation frontier all edit the same functions ( Re: the open question on the early return — the value is cached by the |
Linear: ENG-10093
Description
Two hot paths ran
_isinstance(value, type, nested=1), which walks every element of list/dict values, only to gate a diagnostic log that never changes behavior:BaseState.__setattr__validated the full container on every assignment.ComputedVar.__get__/AsyncComputedVar.__get__validated the return type on every access, including cache hits.Changes:
1in dev and0in prod (new cached_validation_depth()helper inreflex_base.utils.types), so production only checks the outer type. Dev keeps the exact same diagnostics as before.Benchmarks (GitHub Actions runner, run, 2 passes each)
cProfile (3 assigns + 20 cached reads, dev): main spends 3.06s in 10.5M calls dominated by
_isinstance(510k calls); with this PR the cached-read side disappears (_check_deprecated_return_typeno longer on the cache-hit path) and in prod the whole workload is 538 function calls / 0.003s.Type of change
Changes To Core Features:
tests/units/reflex_base/vars/test_base.py: return type checked on recompute, not on cache hits (sync, async, andcache=False).tests/units/reflex_base/utils/test_types.py:_validation_depth()is 0 in prod / 1 in dev.tests/units/test_state.py: wrong-typed assignment still logs an error in dev.Note: behavior-wise, a wrong-typed computed var now logs once per recompute instead of once per access, and prod no longer walks container elements for the log-only check. No exceptions or control flow depend on these checks.
🤖 Generated with Claude Code
https://claude.ai/code/session_01G8cXh3TUbNtbjm2ERqE62X
Generated by Claude Code