Compare commits

...

4 Commits

Author SHA1 Message Date
Masen Furer
cc191f6935
test_lifespan: stop periodic events
avoid lingering events after getting the information we came for
2025-01-07 10:43:23 -08:00
Masen Furer
6aabb57cf3
Use default single-retry for any RedisError
Using the default Retry means that async and sync clients get the appropriate type of Retry
2025-01-06 16:19:38 -08:00
Masen Furer
7419efeae3
retry on any redis error 2025-01-06 15:49:42 -08:00
Masen Furer
c902e6dd45
Enable automatic retry on redis errors
ExponentialBackoff 3x retry for BusyLoadingError, ConnectionError, and TimeoutError
2025-01-06 15:36:28 -08:00
2 changed files with 25 additions and 12 deletions

View File

@ -28,8 +28,8 @@ import typer
from alembic.util.exc import CommandError from alembic.util.exc import CommandError
from packaging import version from packaging import version
from redis import Redis as RedisSync from redis import Redis as RedisSync
from redis import exceptions
from redis.asyncio import Redis from redis.asyncio import Redis
from redis.exceptions import RedisError
from reflex import constants, model from reflex import constants, model
from reflex.compiler import templates from reflex.compiler import templates
@ -333,10 +333,11 @@ def get_redis() -> Redis | None:
Returns: Returns:
The asynchronous redis client. The asynchronous redis client.
""" """
if isinstance((redis_url_or_options := parse_redis_url()), str): if (redis_url := parse_redis_url()) is not None:
return Redis.from_url(redis_url_or_options) return Redis.from_url(
elif isinstance(redis_url_or_options, dict): redis_url,
return Redis(**redis_url_or_options) retry_on_error=[RedisError],
)
return None return None
@ -346,14 +347,15 @@ def get_redis_sync() -> RedisSync | None:
Returns: Returns:
The synchronous redis client. The synchronous redis client.
""" """
if isinstance((redis_url_or_options := parse_redis_url()), str): if (redis_url := parse_redis_url()) is not None:
return RedisSync.from_url(redis_url_or_options) return RedisSync.from_url(
elif isinstance(redis_url_or_options, dict): redis_url,
return RedisSync(**redis_url_or_options) retry_on_error=[RedisError],
)
return None return None
def parse_redis_url() -> str | dict | None: def parse_redis_url() -> str | None:
"""Parse the REDIS_URL in config if applicable. """Parse the REDIS_URL in config if applicable.
Returns: Returns:
@ -387,7 +389,7 @@ async def get_redis_status() -> dict[str, bool | None]:
redis_client.ping() redis_client.ping()
else: else:
status = None status = None
except exceptions.RedisError: except RedisError:
status = False status = False
return {"redis": status} return {"redis": status}

View File

@ -43,6 +43,8 @@ def LifespanApp():
lifespan_task_global = 0 lifespan_task_global = 0
class LifespanState(rx.State): class LifespanState(rx.State):
interval: int = 100
@rx.var @rx.var
def task_global(self) -> int: def task_global(self) -> int:
return lifespan_task_global return lifespan_task_global
@ -59,7 +61,15 @@ def LifespanApp():
return rx.vstack( return rx.vstack(
rx.text(LifespanState.task_global, id="task_global"), rx.text(LifespanState.task_global, id="task_global"),
rx.text(LifespanState.context_global, id="context_global"), rx.text(LifespanState.context_global, id="context_global"),
rx.moment(interval=100, on_change=LifespanState.tick), rx.button(
rx.moment(
interval=LifespanState.interval, on_change=LifespanState.tick
),
on_click=LifespanState.set_interval( # type: ignore
rx.cond(LifespanState.interval, 0, 100)
),
id="toggle-tick",
),
) )
app = rx.App() app = rx.App()
@ -108,6 +118,7 @@ async def test_lifespan(lifespan_app: AppHarness):
original_task_global_text = task_global.text original_task_global_text = task_global.text
original_task_global_value = int(original_task_global_text) original_task_global_value = int(original_task_global_text)
lifespan_app.poll_for_content(task_global, exp_not_equal=original_task_global_text) lifespan_app.poll_for_content(task_global, exp_not_equal=original_task_global_text)
driver.find_element(By.ID, "toggle-tick").click() # avoid teardown errors
assert lifespan_app.app_module.lifespan_task_global > original_task_global_value # type: ignore assert lifespan_app.app_module.lifespan_task_global > original_task_global_value # type: ignore
assert int(task_global.text) > original_task_global_value assert int(task_global.text) > original_task_global_value