Skip to content

fix: deliver linked state updates to all clients - #6934

Open
benedikt-bartscher wants to merge 9 commits into
reflex-dev:mainfrom
benedikt-bartscher:shared-state-redis-fix
Open

fix: deliver linked state updates to all clients#6934
benedikt-bartscher wants to merge 9 commits into
reflex-dev:mainfrom
benedikt-bartscher:shared-state-redis-fix

Conversation

@benedikt-bartscher

@benedikt-bartscher benedikt-bartscher commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Review in cubic

@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends shared-state fan-out to clients connected through other backend instances and strengthens Redis token ownership reconciliation.

  • Adds an asynchronous token-manager connectivity contract used by shared-state updates.
  • Refreshes cross-instance ownership records and cleans up superseded cache mappings.
  • Reorders disconnect cleanup and uses conditional Redis restoration to avoid overwriting newer owners.
  • Adds unit and Redis-backed regression coverage for ownership, reconnection, and fan-out behavior.

Confidence Score: 4/5

The PR is not yet safe to merge because an ownership transition between the connectivity check and update emission can still cause a linked client to miss its state update.

The shared-state task checks connectivity before entering the state-update path, while emission later resolves routing independently; a client that moves backend instances in that interval can still have the delta sent using stale ownership.

Files Needing Attention: reflex/istate/shared.py

Important Files Changed

Filename Overview
reflex/istate/shared.py Cross-instance fan-out now checks distributed connectivity, but the check remains separate from eventual update routing, leaving the previously reported ownership-change race outstanding.
reflex/utils/token_manager.py Adds distributed connectivity checks and conditional ownership recovery while addressing the previously reported stale-cache and delayed-deletion paths.
tests/units/istate/test_shared.py Adds focused coverage for local and cross-instance shared-state fan-out.
tests/units/utils/test_token_manager.py Adds regression coverage for disconnect ordering, stale ownership records, delayed notifications, and conditional restoration.

Reviews (8): Last reviewed commit: "wip" | Re-trigger Greptile

Comment thread reflex/istate/shared.py Outdated
Comment thread reflex/istate/shared.py Outdated
Comment thread reflex/istate/shared.py
@codspeed-hq

codspeed-hq Bot commented Aug 24, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 27 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing benedikt-bartscher:shared-state-redis-fix (a4cb90c) with main (12d29c7)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@benedikt-bartscher
benedikt-bartscher marked this pull request as ready for review August 24, 2026 10:31
@benedikt-bartscher
benedikt-bartscher requested a review from a team as a code owner August 24, 2026 10:31
Comment thread reflex/utils/token_manager.py

@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 4 files

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

Re-trigger cubic

Comment thread reflex/utils/token_manager.py Outdated
Comment thread tests/units/utils/test_token_manager.py
Comment thread reflex/utils/token_manager.py Outdated
Comment thread reflex/istate/shared.py
async def _update_client(token: str):
# Don't send updates for disconnected clients; emit_update relays the
# delta to the owning instance if the socket lives elsewhere.
if not await token_manager.is_token_connected(token):

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.

P1 Owner changes misroute updates

If a linked client disconnects from one backend and reconnects to another after this connectivity check but before modify_state emits the delta, emit_lost_and_found reuses the cached former owner without refreshing it, causing the update to be published to the old backend and missed by the reconnected client.

Knowledge Base Used: State management and event processing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The check itself refreshes the record from Redis, so staleness can only develop while modify_state runs. If the owner moves in that window, the publish lands at the former owner, which no longer has a local mapping (disconnect drops it before any Redis I/O), re-resolves the owner with a fresh Redis fetch, and forwards the update — delivered with one extra hop. If the former owner died instead, its Redis record survives, so the reconnecting client receives a new token via duplicate detection and the old token is abandoned. Refreshing before publish would add a GET per relayed update and still be TOCTOU; the one-hop forwarding is what closes this gap.

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.

You're right. I missed the forwarding behavior in emit_update: the connectivity check only establishes a best-effort target. If ownership changes during modify_state, the stale target receives the message, sees no local mapping because disconnect removes it before Redis I/O, refreshes the owner, and forwards the update to the new instance. If the former instance has died, reconnect duplicate detection assigns a new token, so the old token has no live client. Refreshing before publish would add a Redis GET while remaining inherently TOCTOU. I'll withdraw this finding; no code change is needed.

Comment thread reflex/utils/token_manager.py

@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 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread reflex/utils/token_manager.py
Disconnects pop the local mapping before touching redis, so a self-owned
record still present when a del notification arrives was created by a
newer link and refers to a live socket. Re-store it instead of dropping
it, which previously broke linked-update delivery for clients that
relinked before the notification was processed.
Comment thread reflex/utils/token_manager.py Outdated

@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 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread reflex/utils/token_manager.py Outdated
…claims

The keep-alive restore in _handle_socket_record_del is only legitimate
when the key is genuinely gone. A plain SET could overwrite a record
written meanwhile by another instance claiming the expired token, or by
a concurrent relink on this instance. With NX, any newer write wins, and
a wrongful delete of an owned key self-heals via the del notification.

@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 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread reflex/utils/token_manager.py Outdated
Comment thread reflex/utils/token_manager.py Outdated
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.

1 participant