Skip to content

fix: rehydrate state trees that expire during a background task - #7585

Merged
masenf merged 5 commits into
reflex-dev:mainfrom
FarhanAliRaza:farhan/7478-background-rehydrate
Oct 19, 2026
Merged

masenf merged 5 commits into
reflex-dev:mainfrom
FarhanAliRaza:farhan/7478-background-rehydrate

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A background task releases the state lock while it runs. If the session expires in that time, async with self gets a recreated tree. The delta then carries only the handler's changes, and the browser keeps the values of the expired session (#7478).

A foreground event already recovers from this. When the rx.State root has empty router_data, _execute_event runs _rehydrate. This PR makes StateProxy.__aenter__ use the same signal:

  • It restores the router from the event context.
  • It calls BaseStateEventProcessor._rehydrate, now a staticmethod because it did not use self.

The hydrate snapshot carries is_hydrated=false, so the frontend does not write client storage. The router, the dynamic route args and rx.LocalStorage/rx.Cookie values stay correct. The result is the same as a foreground event on main today.

Supersedes #7543. That PR tracks recreated states in every manager and adds a Redis manifest key. This PR needs no new tracking state, and it also covers a Redis tree that expired completely.

Closes #7478

Test plan

  • New unit test test_background_task_rehydrates_state_tree_expired_while_unlocked, for the memory and Redis managers. It fails on main and passes with this change.
  • tests/units/istate, tests/units/reflex_base, test_state.py, test_event.py and test_app.py pass. The istate and event processor tests also pass against a live Redis.
  • A manual run in a small app with a root state, a child state, a cross-state computed var, a ComponentState, an rx.LocalStorage var and a dynamic route. The background task sleeps longer than the token TTL.
    • Memory, full expiry: the expired values reset. pid, the router, is_hydrated and the browser's localStorage are kept.
    • Redis, full expiry and partial expiry: the expired values reset, and pid and localStorage are kept.
    • Control, no expiry: the background update sends no rehydrate snapshot.

Known limit, the same as the foreground path on main: on Redis, hydrate sends only the states loaded in the tree, so a sibling state that the task does not load keeps its old value.

View guided diff

A background task releases the state lock while it runs. If the session
expires in that time, `async with self` gets a recreated tree, and the
delta carries only the handler's changes. The browser keeps the values
of the expired session.

A recreated `rx.State` root has empty `router_data`. A foreground event
already uses this signal to rehydrate. `StateProxy.__aenter__` now uses
the same signal: it restores the router from the event context and runs
`BaseStateEventProcessor._rehydrate`. The hydrate snapshot carries
`is_hydrated=false`, so the browser keeps its client storage.

Closes reflex-dev#7478
@FarhanAliRaza
FarhanAliRaza requested a review from a team as a code owner October 16, 2026 19:34

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread reflex/istate/proxy.py
Comment thread news/+background-task-expired-state-rehydrate.bugfix.md Outdated
@greptile-apps

greptile-apps Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no new actionable issues remain.

Summary

Rehydrates loaded states when a background task re-enters an expired session.

  • Background tasks rehydrate state trees that expire while unlocked.

Reviews (4) · Last reviewed commit: "fix: keep a background rehydrate from lo..." · Reviewed by Greptile

Comment thread reflex/istate/proxy.py
Comment thread reflex/istate/proxy.py Outdated
Mark the proxy mutable before the rehydrate, so a failure or a
cancellation goes through __aexit__ and closes the state manager
context.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0593158a5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread reflex/istate/proxy.py
Comment thread reflex/istate/proxy.py
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T19:43:28.539049Z d059315 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@codspeed

codspeed Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ 17 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 148 untouched benchmarks


Comparing FarhanAliRaza:farhan/7478-background-rehydrate (95148bc) with main (75a08e1)

Open in CodSpeed

Oplock keeps the tree cached in the process, so deleting the Redis keys
does not expire it.
@FarhanAliRaza FarhanAliRaza added the on deck PRs lined up to review / merge next label Oct 19, 2026
The background task's route is the one it started on. After the tree
expires, the client may be on another page, so the rehydrate stays
routeless and the client's next event loads its current page.
@masenf masenf added the bug Something isn't working label Oct 19, 2026 — with ChatGPT Codex Connector

@masenf masenf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

@masenf
masenf merged commit d3f8ccb into reflex-dev:main Oct 19, 2026
147 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working on deck PRs lined up to review / merge next

Projects

None yet

2 participants