fix(vars): use monotonic time for computed var intervals#6768
Conversation
Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
Merging this PR will not alter performance
Comparing Footnotes
|
Greptile SummaryThis PR fixes interval-cached computed vars to use
Confidence Score: 5/5Safe to merge — the change is narrowly scoped to the interval-cache path, both sync and async computed var paths are updated symmetrically, and cross-process serialization falls back gracefully to a full recompute. The core logic is correct: the UUID guard cleanly handles legacy and cross-process timestamps, elapsed-time arithmetic is straightforward, and the async path mirrors the sync path exactly. The two pre-existing style observations were already raised in a prior review thread and do not affect correctness. No files require special attention. Important Files Changed
Reviews (2): Last reviewed commit: "fix(vars): use monotonic time for comput..." | Re-trigger Greptile |
| datetime_mock = mocker.patch("reflex_base.vars.base.datetime.datetime") | ||
| datetime_mock.now.return_value = initial_wall_time | ||
|
|
||
| state = StateTest() | ||
| if is_async: | ||
| assert await state.async_cv == 1 | ||
| else: | ||
| assert state.cv == 1 | ||
|
|
||
| monotonic_time += monotonic_delta | ||
| datetime_mock.now.return_value = initial_wall_time + wall_clock_delta |
There was a problem hiding this comment.
Dead
datetime mock — wall-clock immunity not actually verified
reflex_base.vars.base.datetime.datetime is patched here, but the updated production code never calls datetime.datetime.now() in the interval-check path anymore. The mock has no effect on execution, so this test does not actually verify that wall-clock changes are ignored — it only verifies that time.monotonic() controls expiry. The wall_clock_delta parameter is therefore inert: the test passes identically whether or not the datetime patch is applied, and a future regression that re-introduces a datetime.datetime.now() call alongside time.monotonic() would not be caught by this test. Either drop the datetime mock (and rename the test parameters/docstring accordingly) or add an assertion that datetime_mock.now was not called after the interval check.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| elapsed = time.monotonic() - last_updated[1] | ||
| return elapsed < 0 or elapsed > self._update_interval.total_seconds() |
There was a problem hiding this comment.
elapsed < 0 is unreachable once the UUID guard passes. Within a single process, time.monotonic() is guaranteed to be non-decreasing, so subtracting a previously recorded value from the same process's clock will always yield a non-negative result. Consider removing the dead condition to keep the expression clean.
| elapsed = time.monotonic() - last_updated[1] | |
| return elapsed < 0 or elapsed > self._update_interval.total_seconds() | |
| elapsed = time.monotonic() - last_updated[1] | |
| return elapsed > self._update_interval.total_seconds() |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
Cached computed vars used
datetime.now()to decide when an update interval hadelapsed. Wall-clock adjustments could therefore refresh a value early or keep it
cached longer than requested.
This change:
time.monotonic()for synchronous andasynchronous computed vars
cross-process persisted timestamps instead of comparing incompatible clocks
incompatible persisted timestamp formats
Because monotonic clocks cannot be compared across processes, an interval-cached
value restored in another worker now recomputes on first access. Subsequent interval
checks use elapsed time within that process.
Testing
uv run pytest tests/units/vars/test_base.py -q(18 passed)uv run pytest tests/units --cov --no-cov-on-fail --cov-report=(6630 passed,17 skipped, 78.07% coverage)
uv run ruff check .uv run ruff format --check .uv run pyright reflex tests(0 errors)uv run towncrier check --config pyproject.toml --dir packages/reflex-base --compare-with origin/mainThe regression tests fail against the previous implementation and pass with this
change.