Skip to content

Commit 5be0918

Browse files
committed
flaky test nad explicit dispatch fuction
1 parent dd6a750 commit 5be0918

File tree

2 files changed

+78
-16
lines changed

2 files changed

+78
-16
lines changed

reflex/compiler/plugins.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from collections.abc import AsyncGenerator, Sequence
77
from contextvars import ContextVar, Token
88
from types import TracebackType
9-
from typing import Any, ClassVar, Protocol, TypeAlias
9+
from typing import Any, ClassVar, Literal, Protocol, TypeAlias, cast, overload
1010

1111
from reflex_core.components.component import BaseComponent, Component, StatefulComponent
1212
from reflex_core.utils.imports import ParsedImportDict, collapse_imports, merge_imports
@@ -62,18 +62,67 @@ class CompilerHooks:
6262

6363
plugins: tuple[CompilerPlugin, ...] = ()
6464

65+
@overload
66+
async def _dispatch(
67+
self,
68+
hook_name: str,
69+
*args: Any,
70+
stop_on_result: Literal[False] = False,
71+
**kwargs: Any,
72+
) -> list[Any]: ...
73+
74+
@overload
75+
async def _dispatch(
76+
self,
77+
hook_name: str,
78+
*args: Any,
79+
stop_on_result: Literal[True],
80+
**kwargs: Any,
81+
) -> Any | None: ...
82+
83+
async def _dispatch(
84+
self,
85+
hook_name: str,
86+
*args: Any,
87+
stop_on_result: bool = False,
88+
**kwargs: Any,
89+
) -> list[Any] | Any | None:
90+
"""Dispatch a coroutine hook across all plugins in registration order.
91+
92+
Args:
93+
hook_name: The plugin hook attribute to invoke.
94+
*args: Positional arguments forwarded to the hook.
95+
stop_on_result: Whether to return immediately on the first non-None
96+
result instead of collecting all results.
97+
**kwargs: Keyword arguments forwarded to the hook.
98+
99+
Returns:
100+
When ``stop_on_result`` is false, a list of hook return values in
101+
registration order. Otherwise, the first non-None result, or
102+
``None`` if every plugin returned ``None``.
103+
"""
104+
results: list[Any] = []
105+
for plugin in self.plugins:
106+
result = await getattr(plugin, hook_name)(*args, **kwargs)
107+
if stop_on_result and result is not None:
108+
return result
109+
results.append(result)
110+
return None if stop_on_result else results
111+
65112
async def eval_page(
66113
self,
67114
page_fn: Any,
68115
/,
69116
**kwargs: Any,
70117
) -> PageContext | None:
71118
"""Return the first page context produced by the plugin chain."""
72-
for plugin in self.plugins:
73-
result = await plugin.eval_page(page_fn, **kwargs)
74-
if result is not None:
75-
return result
76-
return None
119+
result = await self._dispatch(
120+
"eval_page",
121+
page_fn,
122+
stop_on_result=True,
123+
**kwargs,
124+
)
125+
return cast(PageContext | None, result)
77126

78127
async def compile_page(
79128
self,
@@ -82,8 +131,7 @@ async def compile_page(
82131
**kwargs: Any,
83132
) -> None:
84133
"""Run all ``compile_page`` hooks in plugin order."""
85-
for plugin in self.plugins:
86-
await plugin.compile_page(page_ctx, **kwargs)
134+
await self._dispatch("compile_page", page_ctx, **kwargs)
87135

88136
async def compile_component(
89137
self,

tests/units/istate/manager/test_redis.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -306,9 +306,16 @@ async def test_oplock_contention_queue(
306306
state_manager_2._oplock_enabled = True
307307

308308
modify_started = asyncio.Event()
309-
modify_2_started = asyncio.Event()
309+
contenders_started = asyncio.Event()
310310
modify_1_continue = asyncio.Event()
311311
modify_2_continue = asyncio.Event()
312+
contender_started_count = 0
313+
314+
def mark_contender_started() -> None:
315+
nonlocal contender_started_count
316+
contender_started_count += 1
317+
if contender_started_count == 2:
318+
contenders_started.set()
312319

313320
async def modify_1():
314321
async with state_manager_redis.modify_state(
@@ -321,7 +328,7 @@ async def modify_1():
321328

322329
async def modify_2():
323330
await modify_started.wait()
324-
modify_2_started.set()
331+
mark_contender_started()
325332
async with state_manager_2.modify_state(
326333
_substate_key(token, root_state),
327334
) as new_state:
@@ -331,7 +338,7 @@ async def modify_2():
331338

332339
async def modify_3():
333340
await modify_started.wait()
334-
modify_2_started.set()
341+
mark_contender_started()
335342
async with state_manager_2.modify_state(
336343
_substate_key(token, root_state),
337344
) as new_state:
@@ -343,7 +350,7 @@ async def modify_3():
343350
task_2 = asyncio.create_task(modify_2())
344351
task_3 = asyncio.create_task(modify_3())
345352

346-
await modify_2_started.wait()
353+
await contenders_started.wait()
347354

348355
# Let modify 1 complete
349356
modify_1_continue.set()
@@ -407,9 +414,16 @@ async def test_oplock_contention_no_lease(
407414
state_manager_3._oplock_enabled = True
408415

409416
modify_started = asyncio.Event()
410-
modify_2_started = asyncio.Event()
417+
contenders_started = asyncio.Event()
411418
modify_1_continue = asyncio.Event()
412419
modify_2_continue = asyncio.Event()
420+
contender_started_count = 0
421+
422+
def mark_contender_started() -> None:
423+
nonlocal contender_started_count
424+
contender_started_count += 1
425+
if contender_started_count == 2:
426+
contenders_started.set()
413427

414428
async def modify_1():
415429
async with state_manager_redis.modify_state(
@@ -422,7 +436,7 @@ async def modify_1():
422436

423437
async def modify_2():
424438
await modify_started.wait()
425-
modify_2_started.set()
439+
mark_contender_started()
426440
async with state_manager_2.modify_state(
427441
_substate_key(token, root_state),
428442
) as new_state:
@@ -432,7 +446,7 @@ async def modify_2():
432446

433447
async def modify_3():
434448
await modify_started.wait()
435-
modify_2_started.set()
449+
mark_contender_started()
436450
async with state_manager_3.modify_state(
437451
_substate_key(token, root_state),
438452
) as new_state:
@@ -444,7 +458,7 @@ async def modify_3():
444458
task_2 = asyncio.create_task(modify_2())
445459
task_3 = asyncio.create_task(modify_3())
446460

447-
await modify_2_started.wait()
461+
await contenders_started.wait()
448462

449463
# Let modify 1 complete
450464
modify_1_continue.set()

0 commit comments

Comments
 (0)