Skip to content

Commit dbde271

Browse files
Treat async generator reuse runtime errors as non-retryable
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 3277cbc commit dbde271

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

‎hyperbrowser/client/polling.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ def _is_reused_coroutine_runtime_error(exc: Exception) -> bool:
128128
return "coroutine" in normalized_message and "already awaited" in normalized_message
129129

130130

131+
def _is_async_generator_reuse_runtime_error(exc: Exception) -> bool:
132+
if not isinstance(exc, RuntimeError):
133+
return False
134+
normalized_message = str(exc).lower()
135+
return (
136+
"asynchronous generator" in normalized_message
137+
and "already running" in normalized_message
138+
)
139+
140+
131141
def _is_async_loop_contract_runtime_error(exc: Exception) -> bool:
132142
if not isinstance(exc, RuntimeError):
133143
return False
@@ -148,6 +158,8 @@ def _is_retryable_exception(exc: Exception) -> bool:
148158
return False
149159
if _is_reused_coroutine_runtime_error(exc):
150160
return False
161+
if _is_async_generator_reuse_runtime_error(exc):
162+
return False
151163
if _is_async_loop_contract_runtime_error(exc):
152164
return False
153165
if isinstance(exc, ConcurrentCancelledError):

‎tests/test_polling.py‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,24 @@ def operation() -> str:
11611161
assert attempts["count"] == 1
11621162

11631163

1164+
def test_retry_operation_does_not_retry_async_generator_reuse_runtime_errors():
1165+
attempts = {"count": 0}
1166+
1167+
def operation() -> str:
1168+
attempts["count"] += 1
1169+
raise RuntimeError("anext(): asynchronous generator is already running")
1170+
1171+
with pytest.raises(RuntimeError, match="asynchronous generator is already running"):
1172+
retry_operation(
1173+
operation_name="sync retry async-generator-reuse runtime error",
1174+
operation=operation,
1175+
max_attempts=5,
1176+
retry_delay_seconds=0.0001,
1177+
)
1178+
1179+
assert attempts["count"] == 1
1180+
1181+
11641182
def test_poll_until_terminal_status_async_does_not_retry_runtime_errors_marked_as_already_awaited():
11651183
async def run() -> None:
11661184
attempts = {"count": 0}
@@ -1184,6 +1202,31 @@ async def get_status() -> str:
11841202
asyncio.run(run())
11851203

11861204

1205+
def test_poll_until_terminal_status_async_does_not_retry_async_generator_reuse_runtime_errors():
1206+
async def run() -> None:
1207+
attempts = {"count": 0}
1208+
1209+
async def get_status() -> str:
1210+
attempts["count"] += 1
1211+
raise RuntimeError("anext(): asynchronous generator is already running")
1212+
1213+
with pytest.raises(
1214+
RuntimeError, match="asynchronous generator is already running"
1215+
):
1216+
await poll_until_terminal_status_async(
1217+
operation_name="async poll async-generator-reuse runtime error",
1218+
get_status=get_status,
1219+
is_terminal_status=lambda value: value == "completed",
1220+
poll_interval_seconds=0.0001,
1221+
max_wait_seconds=1.0,
1222+
max_status_failures=5,
1223+
)
1224+
1225+
assert attempts["count"] == 1
1226+
1227+
asyncio.run(run())
1228+
1229+
11871230
def test_retry_operation_does_not_retry_runtime_errors_for_loop_mismatch():
11881231
attempts = {"count": 0}
11891232

0 commit comments

Comments
 (0)