Sfoglia il codice sorgente

[ENG-4921] Avoid infinite loop in _mark_dirty_computed_vars (#5058)

The loop was adding dirty var names from other states to be reconsidered in the
loop, but when the other state's dirty var name is a var in the current state,
this loop will never exit.
Masen Furer 1 mese fa
parent
commit
d0ac5415b7
2 ha cambiato i file con 20 aggiunte e 8 eliminazioni
  1. 4 2
      reflex/state.py
  2. 16 6
      tests/units/test_state.py

+ 4 - 2
reflex/state.py

@@ -1873,11 +1873,13 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
                         tuple(state_name.split("."))
                         tuple(state_name.split("."))
                     )
                     )
                 defining_state.dirty_vars.add(cvar)
                 defining_state.dirty_vars.add(cvar)
-                dirty_vars.add(cvar)
                 actual_var = defining_state.computed_vars.get(cvar)
                 actual_var = defining_state.computed_vars.get(cvar)
                 if actual_var is not None:
                 if actual_var is not None:
                     actual_var.mark_dirty(instance=defining_state)
                     actual_var.mark_dirty(instance=defining_state)
-                if defining_state is not self:
+                if defining_state is self:
+                    dirty_vars.add(cvar)
+                else:
+                    # mark dirty where this var is defined
                     defining_state._mark_dirty()
                     defining_state._mark_dirty()
 
 
     def _expired_computed_vars(self) -> set[str]:
     def _expired_computed_vars(self) -> set[str]:

+ 16 - 6
tests/units/test_state.py

@@ -3943,16 +3943,25 @@ async def test_async_computed_var_get_state(mock_app: rx.App, token: str):
 class Table(rx.ComponentState):
 class Table(rx.ComponentState):
     """A table state."""
     """A table state."""
 
 
-    data: ClassVar[Var]
+    _data: ClassVar[Var]
 
 
     @rx.var(cache=True, auto_deps=False)
     @rx.var(cache=True, auto_deps=False)
-    async def rows(self) -> list[dict[str, Any]]:
+    async def data(self) -> list[dict[str, Any]]:
         """Computed var over the given rows.
         """Computed var over the given rows.
 
 
         Returns:
         Returns:
             The data rows.
             The data rows.
         """
         """
-        return await self.get_var_value(self.data)
+        return await self.get_var_value(self._data)
+
+    @rx.var
+    async def foo(self) -> list[dict[str, Any]]:
+        """Another computed var that depends on data in this state.
+
+        Returns:
+            The data rows.
+        """
+        return await self.data
 
 
     @classmethod
     @classmethod
     def get_component(cls, data: Var) -> rx.Component:
     def get_component(cls, data: Var) -> rx.Component:
@@ -3964,8 +3973,8 @@ class Table(rx.ComponentState):
         Returns:
         Returns:
             The component.
             The component.
         """
         """
-        cls.data = data
-        cls.computed_vars["rows"].add_dependency(cls, data)
+        cls._data = data
+        cls.computed_vars["data"].add_dependency(cls, data)
         return rx.foreach(data, lambda d: rx.text(d.to_string()))
         return rx.foreach(data, lambda d: rx.text(d.to_string()))
 
 
 
 
@@ -3992,7 +4001,8 @@ async def test_async_computed_var_get_var_value(mock_app: rx.App, token: str):
     assert comp_state.dirty_vars == set()
     assert comp_state.dirty_vars == set()
 
 
     other_state.data.append({"foo": "baz"})
     other_state.data.append({"foo": "baz"})
-    assert "rows" in comp_state.dirty_vars
+    assert "data" in comp_state.dirty_vars
+    assert "foo" in comp_state.dirty_vars
 
 
 
 
 def test_computed_var_mutability() -> None:
 def test_computed_var_mutability() -> None: