Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions reflex/istate/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,10 +338,11 @@ async def _modify_linked_states(
linked_state_name
)
)
# TODO: Avoid always fetched linked states, it should be based on
# whether the state is accessed, however then `get_state` would need
# to know how to fetch in a linked state.
original_state = await self.get_state(linked_state_cls)
try:
original_state = self._get_state_from_cache(linked_state_cls)
except ValueError:
# This state wasn't required for processing the event.
continue
linked_state = await original_state._internal_patch_linked_state(
linked_token
)
Expand Down Expand Up @@ -400,3 +401,9 @@ def __init_subclass__(cls, **kwargs):
root_state = cls.get_root_state()
if root_state.backend_vars["_reflex_internal_links"] is None:
root_state.backend_vars["_reflex_internal_links"] = {}
if root_state is State:
# Always fetch SharedStateBaseInternal to access
# `_modify_linked_states` without having to use `.get_state()` which
# pulls in all linked states and substates which may not actually be
# accessed for this event.
root_state._always_dirty_substates.add(SharedStateBaseInternal.get_name())
29 changes: 29 additions & 0 deletions reflex/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -2467,6 +2467,35 @@ class State(BaseState):
# Maps the state full_name to an arbitrary token it is linked to for shared state.
_reflex_internal_links: dict[str, str] | None = None

@_override_base_method
async def _get_state_from_redis(self, state_cls: type[T_STATE]) -> T_STATE:
"""Get a state instance from redis with linking support.

Args:
state_cls: The class of the state.

Returns:
The instance of state_cls associated with this state's client_token.
"""
state_instance = await super()._get_state_from_redis(state_cls)
if (
self._reflex_internal_links
and (
linked_token := self._reflex_internal_links.get(
state_cls.get_full_name()
)
)
is not None
and (
internal_patch_linked_state := getattr(
state_instance, "_internal_patch_linked_state", None
)
)
is not None
):
return await internal_patch_linked_state(linked_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.

logic: _internal_patch_linked_state requires being called inside a _modify_linked_states context (it checks self._exit_stack and self._held_locks on line 260-262 of shared.py). However, _get_state_from_redis can be called from get_state() outside of this context, which will cause a ReflexRuntimeError at runtime.

The state_instance fetched from Redis won't have _exit_stack or _held_locks set (these are explicitly excluded in __getstate__ on line 127-128 of shared.py), so calling this method will fail.

Prompt To Fix With AI
This is a comment left during a code review.
Path: reflex/state.py
Line: 2496:2496

Comment:
**logic:** `_internal_patch_linked_state` requires being called inside a `_modify_linked_states` context (it checks `self._exit_stack` and `self._held_locks` on line 260-262 of shared.py). However, `_get_state_from_redis` can be called from `get_state()` outside of this context, which will cause a `ReflexRuntimeError` at runtime.

The `state_instance` fetched from Redis won't have `_exit_stack` or `_held_locks` set (these are explicitly excluded in `__getstate__` on line 127-128 of shared.py), so calling this method will fail.

How can I resolve this? If you propose a fix, please make it concise.

return state_instance

@event
def set_is_hydrated(self, value: bool) -> None:
"""Set the hydrated state.
Expand Down
Loading