Skip to content

DO NOT MERGE - Disconnect shared state - #6942

Draft
benedikt-bartscher wants to merge 13 commits into
reflex-dev:mainfrom
benedikt-bartscher:disconnect-shared-state
Draft

benedikt-bartscher wants to merge 13 commits into
reflex-dev:mainfrom
benedikt-bartscher:disconnect-shared-state

Conversation

@benedikt-bartscher

@benedikt-bartscher benedikt-bartscher commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

based on #6934

Review in cubic

…ce period

Resolves the TODO in _do_update_other_tokens: tokens of clients that
disconnected and did not reconnect within
REFLEX_SHARED_STATE_DISCONNECT_GRACE (default 30s, 0 disables) are
removed from the _linked_from subscriber sets of all shared states the
client was linked to, so shared mutations stop fanning out modify_state
churn for them. The new SharedState._on_subscriber_disconnected hook
lets shared states clean up per-client data (e.g. presence bookkeeping).

Safety: a reconnect within the grace (any instance, checked via
is_token_connected) is a no-op; a client reaped too eagerly
re-subscribes automatically on its next event through
_internal_patch_linked_state. A new disconnect for the same token
restarts the grace. Only the shared subscriber sets are touched, never
the client's own _reflex_internal_links.
@greptile-apps

greptile-apps Bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change affects shared-state subscriptions across apps that use linked shared states.

The PR should not merge until disconnect cleanup can still find subscriptions after the client's saved state expires.

Findings

  1. P1 Expired links prevent cleanup ▶

Summary

Adds delayed cleanup for clients that disconnect from shared states.

  • Disconnected clients leave shared states after a reconnect grace period.

Reviews (9) · Last reviewed commit: "Merge remote-tracking branch 'upstream/m..." · Reviewed by Greptile

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

codspeed Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning

⚠️ 12 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 148 untouched benchmarks


Comparing benedikt-bartscher:disconnect-shared-state (d0e978d) with main (656e8c7)

Open in CodSpeed

Comment thread reflex/istate/shared.py Outdated
Comment thread reflex/istate/shared.py
Comment on lines +148 to +153
root_state = await app.state_manager.get_state(
BaseStateToken(ident=token, cls=app._state)
)
if not isinstance(root_state, State):
return
links = dict(root_state._reflex_internal_links or {})

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 Expired links prevent cleanup

_reap_disconnected_client relies on the client's saved state still existing after the grace period. Redis expires that state based on its last write, not when the socket disconnects. If a client disconnects shortly before its state expires, get_state creates a fresh state with no saved _reflex_internal_links. Cleanup then skips every subscription, leaving the client in an active shared state's _linked_from and never calling _on_subscriber_disconnected.

Keep the link information available until disconnect cleanup finishes.

…halfway

Recheck the connection under the shared-state lock before unlinking, and
let a reap past its grace period finish instead of cancelling it on the
next disconnect.
Comment thread reflex/istate/shared.py Outdated
DISCONNECT_REAP_TASKS now keeps the latest reap per token until it finishes. Under each shared state's lock, a reap rechecks that the client is still gone and that no newer reap replaced it; only then does it join STARTED_REAP_TASKS and stop being cancellable, so a reap superseded while waiting for the lock leaves the unlink to the new grace period.
…tate

on_disconnect now cancels the client's page-load chains (only for its current local socket) and then schedules the shared-state reap unconditionally, as before.

This branch has not been deployed

No deployments
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