fix duplicate tab issue#4607
Conversation
|
Revisiting this PR, is there ever a case where we wouldn't get |
CodSpeed Performance ReportMerging #4607 will not alter performanceComparing Summary
|
There was a problem hiding this comment.
Greptile Summary
This PR implements a comprehensive fix for duplicate browser tab interference during background events (issue #4605). The core problem was that when users duplicated tabs using Ctrl+D or similar methods, both tabs shared the same client_token from sessionStorage, causing background tasks from one tab to interfere with the other's state updates.
The solution introduces a new TokenManager system with the following key components:
-
TokenManager Architecture: Abstract base class with
LocalTokenManager(single-worker) andRedisTokenManager(distributed) implementations that detect when the same token is used by multiple Socket.IO session IDs, indicating duplicate tabs. -
WebSocket Connection Enhancement: Modified the client-side state.js to send the client token as a query parameter during WebSocket connection establishment, enabling server-side duplicate detection at connection time rather than during event processing.
-
Duplicate Detection & Resolution: When a duplicate token is detected, the server automatically generates a new UUID4 token and emits a 'new_token' event to the client, which updates both the global token variable and sessionStorage.
-
Event Flow Restructuring: Moved token-to-session-ID linking from
on_eventtoon_connectin the EventNamespace, ensuring duplicate detection happens immediately upon connection rather than during background task execution. -
Testing Infrastructure: Added comprehensive unit tests covering both LocalTokenManager and RedisTokenManager implementations, including error handling, Redis fallback mechanisms, and cleanup scenarios.
-
Testing Framework Integration: Enhanced the AppHarness testing class to reset the TokenManager instance when the backend state manager is reset, preventing event loop affinity issues during tests.
This architectural change maintains backward compatibility while providing robust duplicate tab isolation for both single-worker and distributed Reflex deployments.
Confidence score: 4/5
- This is a well-architected solution that addresses the root cause of tab interference issues with proper error handling and fallback mechanisms.
- The comprehensive test coverage and multi-environment support (local vs Redis) demonstrates thorough consideration of edge cases.
- Files requiring more attention:
reflex/app.pyfor the core EventNamespace changes andreflex/utils/token_manager.pyfor the new token management logic, as these are critical components that handle the duplicate detection and token generation flow.
5 files reviewed, 2 comments
masenf
left a comment
There was a problem hiding this comment.
functionality works great in my testing; a few comments from an API continuity perspective, since people actually do use this functionality and need it for background tasks and perhaps other cleanup steps
|
|
||
| def on_connect(self, sid: str, environ: dict): | ||
| # Use TokenManager for distributed duplicate tab prevention | ||
| self._token_manager = TokenManager.create() |
There was a problem hiding this comment.
consider putting the TokenManager on App as a real API into this data.
down the road we could even expose helper iterators on this new TokenManager to make cancelling background tasks easier, like session_is_connected and token_is_connected which would save the passed sid/token and yield True as long as these values were still being tracked.
Like so
@rx.event(background=True)
async def thing_checker(self):
for _task_active in app.token_manager.session_is_connected(
sid=self.router_data.session.session_id
):
async with self:
if await self._check_thing():
await self._update_thing()
await asyncio.sleep(CHECK_INTERVAL)lot of possibilities for an asyncio.Event API for getting notified when a session or client disconnects, which people have asked for
| The token to SID mapping dict. | ||
| """ | ||
| # For backward compatibility, expose the underlying dict | ||
| return self._token_manager.token_to_sid |
There was a problem hiding this comment.
should raise a deprecation here and below
Sry to ressurrect this PR. Yes there is a case where a client reconnects without firing "on_disconnect". Specifically when the Backend Process crashes. If the app is used by multiple users, one causing a crash (or restart by external controls, see containers in K8s/docker) will make the tokes/sessions stored in Redis persistent but still linked, meaning after the container restarts it causes a token duplicate, which in turn forces to create a new token (without hydration mind you) causing an inconsitent state between UI and Backend breaking the app. Tested by:
This issue is reproducible 100% on any Version after 0.8.5 (last version before this PR got merged) |
|
@hr-alebel may I suggest opening an issue for this which references the old issue/PR? |
fix #4605