Skip to content

Commit bae1019

Browse files
Treat broken executor errors as non-retryable
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 4a60b0e commit bae1019

2 files changed

Lines changed: 133 additions & 0 deletions

File tree

hyperbrowser/client/polling.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
from concurrent.futures import CancelledError as ConcurrentCancelledError
3+
from concurrent.futures import BrokenExecutor as ConcurrentBrokenExecutor
34
import inspect
45
import math
56
from numbers import Real
@@ -172,6 +173,8 @@ def _is_executor_shutdown_runtime_error(exc: Exception) -> bool:
172173

173174

174175
def _is_retryable_exception(exc: Exception) -> bool:
176+
if isinstance(exc, ConcurrentBrokenExecutor):
177+
return False
175178
if isinstance(exc, (StopIteration, StopAsyncIteration)):
176179
return False
177180
if _is_generator_reentrancy_error(exc):

tests/test_polling.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
from concurrent.futures import CancelledError as ConcurrentCancelledError
3+
from concurrent.futures import BrokenExecutor as ConcurrentBrokenExecutor
34
import math
45
from fractions import Fraction
56

@@ -221,6 +222,26 @@ def get_status() -> str:
221222
assert attempts["count"] == 1
222223

223224

225+
def test_poll_until_terminal_status_does_not_retry_broken_executor_errors():
226+
attempts = {"count": 0}
227+
228+
def get_status() -> str:
229+
attempts["count"] += 1
230+
raise ConcurrentBrokenExecutor("executor is broken")
231+
232+
with pytest.raises(ConcurrentBrokenExecutor, match="executor is broken"):
233+
poll_until_terminal_status(
234+
operation_name="sync poll broken-executor passthrough",
235+
get_status=get_status,
236+
is_terminal_status=lambda value: value == "completed",
237+
poll_interval_seconds=0.0001,
238+
max_wait_seconds=1.0,
239+
max_status_failures=5,
240+
)
241+
242+
assert attempts["count"] == 1
243+
244+
224245
def test_poll_until_terminal_status_retries_rate_limit_errors():
225246
attempts = {"count": 0}
226247

@@ -604,6 +625,24 @@ def operation() -> str:
604625
assert attempts["count"] == 1
605626

606627

628+
def test_retry_operation_does_not_retry_broken_executor_errors():
629+
attempts = {"count": 0}
630+
631+
def operation() -> str:
632+
attempts["count"] += 1
633+
raise ConcurrentBrokenExecutor("executor is broken")
634+
635+
with pytest.raises(ConcurrentBrokenExecutor, match="executor is broken"):
636+
retry_operation(
637+
operation_name="sync retry broken-executor passthrough",
638+
operation=operation,
639+
max_attempts=5,
640+
retry_delay_seconds=0.0001,
641+
)
642+
643+
assert attempts["count"] == 1
644+
645+
607646
def test_retry_operation_retries_server_errors():
608647
attempts = {"count": 0}
609648

@@ -931,6 +970,29 @@ async def get_status() -> str:
931970
asyncio.run(run())
932971

933972

973+
def test_poll_until_terminal_status_async_does_not_retry_broken_executor_errors():
974+
async def run() -> None:
975+
attempts = {"count": 0}
976+
977+
async def get_status() -> str:
978+
attempts["count"] += 1
979+
raise ConcurrentBrokenExecutor("executor is broken")
980+
981+
with pytest.raises(ConcurrentBrokenExecutor, match="executor is broken"):
982+
await poll_until_terminal_status_async(
983+
operation_name="async poll broken-executor passthrough",
984+
get_status=get_status,
985+
is_terminal_status=lambda value: value == "completed",
986+
poll_interval_seconds=0.0001,
987+
max_wait_seconds=1.0,
988+
max_status_failures=5,
989+
)
990+
991+
assert attempts["count"] == 1
992+
993+
asyncio.run(run())
994+
995+
934996
def test_poll_until_terminal_status_async_retries_server_errors():
935997
async def run() -> None:
936998
attempts = {"count": 0}
@@ -1102,6 +1164,27 @@ async def operation() -> str:
11021164
asyncio.run(run())
11031165

11041166

1167+
def test_retry_operation_async_does_not_retry_broken_executor_errors():
1168+
async def run() -> None:
1169+
attempts = {"count": 0}
1170+
1171+
async def operation() -> str:
1172+
attempts["count"] += 1
1173+
raise ConcurrentBrokenExecutor("executor is broken")
1174+
1175+
with pytest.raises(ConcurrentBrokenExecutor, match="executor is broken"):
1176+
await retry_operation_async(
1177+
operation_name="async retry broken-executor passthrough",
1178+
operation=operation,
1179+
max_attempts=5,
1180+
retry_delay_seconds=0.0001,
1181+
)
1182+
1183+
assert attempts["count"] == 1
1184+
1185+
asyncio.run(run())
1186+
1187+
11051188
def test_retry_operation_async_retries_server_errors():
11061189
async def run() -> None:
11071190
attempts = {"count": 0}
@@ -2174,6 +2257,28 @@ def get_next_page(page: int) -> dict:
21742257
assert attempts["count"] == 1
21752258

21762259

2260+
def test_collect_paginated_results_does_not_retry_broken_executor_errors():
2261+
attempts = {"count": 0}
2262+
2263+
def get_next_page(page: int) -> dict:
2264+
attempts["count"] += 1
2265+
raise ConcurrentBrokenExecutor("executor is broken")
2266+
2267+
with pytest.raises(ConcurrentBrokenExecutor, match="executor is broken"):
2268+
collect_paginated_results(
2269+
operation_name="sync paginated broken-executor passthrough",
2270+
get_next_page=get_next_page,
2271+
get_current_page_batch=lambda response: response["current"],
2272+
get_total_page_batches=lambda response: response["total"],
2273+
on_page_success=lambda response: None,
2274+
max_wait_seconds=1.0,
2275+
max_attempts=5,
2276+
retry_delay_seconds=0.0001,
2277+
)
2278+
2279+
assert attempts["count"] == 1
2280+
2281+
21772282
def test_collect_paginated_results_retries_server_errors():
21782283
attempts = {"count": 0}
21792284
collected = []
@@ -2540,6 +2645,31 @@ async def get_next_page(page: int) -> dict:
25402645
asyncio.run(run())
25412646

25422647

2648+
def test_collect_paginated_results_async_does_not_retry_broken_executor_errors():
2649+
async def run() -> None:
2650+
attempts = {"count": 0}
2651+
2652+
async def get_next_page(page: int) -> dict:
2653+
attempts["count"] += 1
2654+
raise ConcurrentBrokenExecutor("executor is broken")
2655+
2656+
with pytest.raises(ConcurrentBrokenExecutor, match="executor is broken"):
2657+
await collect_paginated_results_async(
2658+
operation_name="async paginated broken-executor passthrough",
2659+
get_next_page=get_next_page,
2660+
get_current_page_batch=lambda response: response["current"],
2661+
get_total_page_batches=lambda response: response["total"],
2662+
on_page_success=lambda response: None,
2663+
max_wait_seconds=1.0,
2664+
max_attempts=5,
2665+
retry_delay_seconds=0.0001,
2666+
)
2667+
2668+
assert attempts["count"] == 1
2669+
2670+
asyncio.run(run())
2671+
2672+
25432673
def test_collect_paginated_results_async_retries_server_errors():
25442674
async def run() -> None:
25452675
attempts = {"count": 0}

0 commit comments

Comments
 (0)