Skip to content

Commit 2b0e097

Browse files
committed
Fix StateManager.create to respect explicit memory mode when Redis URL is set
Previously, StateManager.create always overrode the mode to REDIS when a Redis URL was detected, ignoring an explicitly configured memory mode. Now it only auto-promotes to REDIS when state_manager_mode was not explicitly set. Adds a test verifying the explicit mode is honored.
1 parent c0b8742 commit 2b0e097

4 files changed

Lines changed: 36 additions & 3 deletions

File tree

reflex/istate/manager/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ def create(cls, state: type[BaseState]):
4646
InvalidStateManagerModeError: If the state manager mode is invalid.
4747
"""
4848
config = get_config()
49-
if prerequisites.parse_redis_url() is not None:
49+
if (
50+
"state_manager_mode" not in config._non_default_attributes
51+
and prerequisites.parse_redis_url() is not None
52+
):
5053
config.state_manager_mode = constants.StateManagerMode.REDIS
5154
if config.state_manager_mode == constants.StateManagerMode.MEMORY:
5255
from reflex.istate.manager.memory import StateManagerMemory

reflex/istate/manager/_expiration.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
class StateManagerExpiration:
2020
"""Internal base for managers with in-memory state expiration."""
2121

22-
_locked_expiration_poll_interval: ClassVar[float] = 0.1
22+
_locked_expiration_poll_interval: ClassVar[float] = 0.1 # 100 ms
2323
_recheck_expired_locks_on_unlock: ClassVar[bool] = False
2424

2525
token_expiration: int = dataclasses.field(default_factory=_default_token_expiration)
@@ -72,7 +72,8 @@ def _touch_token(self, token: str):
7272
Args:
7373
token: The token that was accessed.
7474
"""
75-
expires_at = time.time() + self.token_expiration
75+
touched_at = time.time()
76+
expires_at = touched_at + self.token_expiration # seconds from last touch
7677
self._token_expires_at[token] = expires_at
7778
self._pending_locked_expirations.discard(token)
7879
heapq.heappush(self._token_expiration_heap, (expires_at, token))

tests/integration/test_memory_state_manager_expiration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def memory_expiration_app(
4848
A running app harness configured to use StateManagerMemory.
4949
"""
5050
monkeypatch.setenv("REFLEX_STATE_MANAGER_MODE", "memory")
51+
# Memory expiration reuses the shared token_expiration config field.
5152
monkeypatch.setenv("REFLEX_REDIS_TOKEN_EXPIRATION", "1")
5253

5354
with app_harness_env.create(

tests/units/test_state.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3596,6 +3596,34 @@ def test_redis_state_manager_config_knobs_invalid_lock_warning_threshold(
35963596
del sys.modules[constants.Config.MODULE]
35973597

35983598

3599+
def test_state_manager_create_respects_explicit_memory_mode_with_redis_url(
3600+
tmp_path, monkeypatch: pytest.MonkeyPatch
3601+
):
3602+
proj_root = tmp_path / "project1"
3603+
proj_root.mkdir()
3604+
3605+
config_string = """
3606+
import reflex as rx
3607+
config = rx.Config(
3608+
app_name="project1",
3609+
)
3610+
"""
3611+
3612+
(proj_root / "rxconfig.py").write_text(dedent(config_string))
3613+
monkeypatch.setenv("REFLEX_STATE_MANAGER_MODE", "memory")
3614+
monkeypatch.setenv("REFLEX_REDIS_URL", "redis://localhost:6379")
3615+
3616+
with chdir(proj_root):
3617+
reflex.config.get_config(reload=True)
3618+
monkeypatch.setattr(prerequisites, "get_redis", mock_redis)
3619+
from reflex.state import State
3620+
3621+
state_manager = StateManager.create(state=State)
3622+
assert isinstance(state_manager, StateManagerMemory)
3623+
3624+
del sys.modules[constants.Config.MODULE]
3625+
3626+
35993627
def test_auto_setters_off(tmp_path):
36003628
proj_root = tmp_path / "project1"
36013629
proj_root.mkdir()

0 commit comments

Comments
 (0)