Skip to content

Commit 85f0342

Browse files
Treat concurrent cancellation errors as non-retryable
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 5927032 commit 85f0342

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,4 +1,5 @@
11
import asyncio
2+
from concurrent.futures import CancelledError as ConcurrentCancelledError
23
import inspect
34
import math
45
from numbers import Real
@@ -113,6 +114,8 @@ def _invoke_non_retryable_callback(
113114

114115

115116
def _is_retryable_exception(exc: Exception) -> bool:
117+
if isinstance(exc, ConcurrentCancelledError):
118+
return False
116119
if isinstance(exc, _NonRetryablePollingError):
117120
return False
118121
if isinstance(exc, (HyperbrowserTimeoutError, HyperbrowserPollingError)):

‎tests/test_polling.py‎

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
from concurrent.futures import CancelledError as ConcurrentCancelledError
23
import math
34
from fractions import Fraction
45

@@ -160,6 +161,26 @@ def get_status_polling_error() -> str:
160161
assert polling_attempts["count"] == 1
161162

162163

164+
def test_poll_until_terminal_status_does_not_retry_concurrent_cancelled_errors():
165+
attempts = {"count": 0}
166+
167+
def get_status() -> str:
168+
attempts["count"] += 1
169+
raise ConcurrentCancelledError()
170+
171+
with pytest.raises(ConcurrentCancelledError):
172+
poll_until_terminal_status(
173+
operation_name="sync poll concurrent-cancelled passthrough",
174+
get_status=get_status,
175+
is_terminal_status=lambda value: value == "completed",
176+
poll_interval_seconds=0.0001,
177+
max_wait_seconds=1.0,
178+
max_status_failures=5,
179+
)
180+
181+
assert attempts["count"] == 1
182+
183+
163184
def test_poll_until_terminal_status_retries_rate_limit_errors():
164185
attempts = {"count": 0}
165186

@@ -489,6 +510,24 @@ def polling_error_operation() -> str:
489510
assert polling_attempts["count"] == 1
490511

491512

513+
def test_retry_operation_does_not_retry_concurrent_cancelled_errors():
514+
attempts = {"count": 0}
515+
516+
def operation() -> str:
517+
attempts["count"] += 1
518+
raise ConcurrentCancelledError()
519+
520+
with pytest.raises(ConcurrentCancelledError):
521+
retry_operation(
522+
operation_name="sync retry concurrent-cancelled passthrough",
523+
operation=operation,
524+
max_attempts=5,
525+
retry_delay_seconds=0.0001,
526+
)
527+
528+
assert attempts["count"] == 1
529+
530+
492531
def test_retry_operation_retries_server_errors():
493532
attempts = {"count": 0}
494533

@@ -726,6 +765,29 @@ async def get_status_polling_error() -> str:
726765
asyncio.run(run())
727766

728767

768+
def test_poll_until_terminal_status_async_does_not_retry_concurrent_cancelled_errors():
769+
async def run() -> None:
770+
attempts = {"count": 0}
771+
772+
async def get_status() -> str:
773+
attempts["count"] += 1
774+
raise ConcurrentCancelledError()
775+
776+
with pytest.raises(ConcurrentCancelledError):
777+
await poll_until_terminal_status_async(
778+
operation_name="async poll concurrent-cancelled passthrough",
779+
get_status=get_status,
780+
is_terminal_status=lambda value: value == "completed",
781+
poll_interval_seconds=0.0001,
782+
max_wait_seconds=1.0,
783+
max_status_failures=5,
784+
)
785+
786+
assert attempts["count"] == 1
787+
788+
asyncio.run(run())
789+
790+
729791
def test_poll_until_terminal_status_async_retries_server_errors():
730792
async def run() -> None:
731793
attempts = {"count": 0}
@@ -834,6 +896,27 @@ async def polling_error_operation() -> str:
834896
asyncio.run(run())
835897

836898

899+
def test_retry_operation_async_does_not_retry_concurrent_cancelled_errors():
900+
async def run() -> None:
901+
attempts = {"count": 0}
902+
903+
async def operation() -> str:
904+
attempts["count"] += 1
905+
raise ConcurrentCancelledError()
906+
907+
with pytest.raises(ConcurrentCancelledError):
908+
await retry_operation_async(
909+
operation_name="async retry concurrent-cancelled passthrough",
910+
operation=operation,
911+
max_attempts=5,
912+
retry_delay_seconds=0.0001,
913+
)
914+
915+
assert attempts["count"] == 1
916+
917+
asyncio.run(run())
918+
919+
837920
def test_retry_operation_async_retries_server_errors():
838921
async def run() -> None:
839922
attempts = {"count": 0}
@@ -1530,6 +1613,28 @@ def get_next_page(page: int) -> dict:
15301613
assert attempts["count"] == 1
15311614

15321615

1616+
def test_collect_paginated_results_does_not_retry_concurrent_cancelled_errors():
1617+
attempts = {"count": 0}
1618+
1619+
def get_next_page(page: int) -> dict:
1620+
attempts["count"] += 1
1621+
raise ConcurrentCancelledError()
1622+
1623+
with pytest.raises(ConcurrentCancelledError):
1624+
collect_paginated_results(
1625+
operation_name="sync paginated concurrent-cancelled passthrough",
1626+
get_next_page=get_next_page,
1627+
get_current_page_batch=lambda response: response["current"],
1628+
get_total_page_batches=lambda response: response["total"],
1629+
on_page_success=lambda response: None,
1630+
max_wait_seconds=1.0,
1631+
max_attempts=5,
1632+
retry_delay_seconds=0.0001,
1633+
)
1634+
1635+
assert attempts["count"] == 1
1636+
1637+
15331638
def test_collect_paginated_results_retries_server_errors():
15341639
attempts = {"count": 0}
15351640
collected = []
@@ -1821,6 +1926,31 @@ async def get_next_page(page: int) -> dict:
18211926
asyncio.run(run())
18221927

18231928

1929+
def test_collect_paginated_results_async_does_not_retry_concurrent_cancelled_errors():
1930+
async def run() -> None:
1931+
attempts = {"count": 0}
1932+
1933+
async def get_next_page(page: int) -> dict:
1934+
attempts["count"] += 1
1935+
raise ConcurrentCancelledError()
1936+
1937+
with pytest.raises(ConcurrentCancelledError):
1938+
await collect_paginated_results_async(
1939+
operation_name="async paginated concurrent-cancelled passthrough",
1940+
get_next_page=get_next_page,
1941+
get_current_page_batch=lambda response: response["current"],
1942+
get_total_page_batches=lambda response: response["total"],
1943+
on_page_success=lambda response: None,
1944+
max_wait_seconds=1.0,
1945+
max_attempts=5,
1946+
retry_delay_seconds=0.0001,
1947+
)
1948+
1949+
assert attempts["count"] == 1
1950+
1951+
asyncio.run(run())
1952+
1953+
18241954
def test_collect_paginated_results_async_retries_server_errors():
18251955
async def run() -> None:
18261956
attempts = {"count": 0}

0 commit comments

Comments
 (0)