Skip to content

Commit d0de1c2

Browse files
Treat SDK timeout and polling errors as non-retryable
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent f690991 commit d0de1c2

2 files changed

Lines changed: 199 additions & 0 deletions

File tree

‎hyperbrowser/client/polling.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ def _invoke_non_retryable_callback(
113113
def _is_retryable_exception(exc: Exception) -> bool:
114114
if isinstance(exc, _NonRetryablePollingError):
115115
return False
116+
if isinstance(exc, (HyperbrowserTimeoutError, HyperbrowserPollingError)):
117+
return False
116118
if isinstance(exc, HyperbrowserError) and exc.status_code is not None:
117119
if isinstance(exc.status_code, bool) or not isinstance(exc.status_code, int):
118120
return True

‎tests/test_polling.py‎

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,44 @@ def get_status() -> str:
122122
assert attempts["count"] == 1
123123

124124

125+
def test_poll_until_terminal_status_does_not_retry_timeout_or_polling_errors():
126+
timeout_attempts = {"count": 0}
127+
128+
def get_status_timeout() -> str:
129+
timeout_attempts["count"] += 1
130+
raise HyperbrowserTimeoutError("timed out internally")
131+
132+
with pytest.raises(HyperbrowserTimeoutError, match="timed out internally"):
133+
poll_until_terminal_status(
134+
operation_name="sync poll timeout passthrough",
135+
get_status=get_status_timeout,
136+
is_terminal_status=lambda value: value == "completed",
137+
poll_interval_seconds=0.0001,
138+
max_wait_seconds=1.0,
139+
max_status_failures=5,
140+
)
141+
142+
assert timeout_attempts["count"] == 1
143+
144+
polling_attempts = {"count": 0}
145+
146+
def get_status_polling_error() -> str:
147+
polling_attempts["count"] += 1
148+
raise HyperbrowserPollingError("upstream polling failure")
149+
150+
with pytest.raises(HyperbrowserPollingError, match="upstream polling failure"):
151+
poll_until_terminal_status(
152+
operation_name="sync poll polling-error passthrough",
153+
get_status=get_status_polling_error,
154+
is_terminal_status=lambda value: value == "completed",
155+
poll_interval_seconds=0.0001,
156+
max_wait_seconds=1.0,
157+
max_status_failures=5,
158+
)
159+
160+
assert polling_attempts["count"] == 1
161+
162+
125163
def test_poll_until_terminal_status_retries_rate_limit_errors():
126164
attempts = {"count": 0}
127165

@@ -325,6 +363,40 @@ def operation() -> str:
325363
assert attempts["count"] == 1
326364

327365

366+
def test_retry_operation_does_not_retry_timeout_or_polling_errors():
367+
timeout_attempts = {"count": 0}
368+
369+
def timeout_operation() -> str:
370+
timeout_attempts["count"] += 1
371+
raise HyperbrowserTimeoutError("timed out internally")
372+
373+
with pytest.raises(HyperbrowserTimeoutError, match="timed out internally"):
374+
retry_operation(
375+
operation_name="sync retry timeout passthrough",
376+
operation=timeout_operation,
377+
max_attempts=5,
378+
retry_delay_seconds=0.0001,
379+
)
380+
381+
assert timeout_attempts["count"] == 1
382+
383+
polling_attempts = {"count": 0}
384+
385+
def polling_error_operation() -> str:
386+
polling_attempts["count"] += 1
387+
raise HyperbrowserPollingError("upstream polling failure")
388+
389+
with pytest.raises(HyperbrowserPollingError, match="upstream polling failure"):
390+
retry_operation(
391+
operation_name="sync retry polling-error passthrough",
392+
operation=polling_error_operation,
393+
max_attempts=5,
394+
retry_delay_seconds=0.0001,
395+
)
396+
397+
assert polling_attempts["count"] == 1
398+
399+
328400
def test_retry_operation_retries_server_errors():
329401
attempts = {"count": 0}
330402

@@ -481,6 +553,47 @@ async def get_status() -> str:
481553
asyncio.run(run())
482554

483555

556+
def test_poll_until_terminal_status_async_does_not_retry_timeout_or_polling_errors():
557+
async def run() -> None:
558+
timeout_attempts = {"count": 0}
559+
560+
async def get_status_timeout() -> str:
561+
timeout_attempts["count"] += 1
562+
raise HyperbrowserTimeoutError("timed out internally")
563+
564+
with pytest.raises(HyperbrowserTimeoutError, match="timed out internally"):
565+
await poll_until_terminal_status_async(
566+
operation_name="async poll timeout passthrough",
567+
get_status=get_status_timeout,
568+
is_terminal_status=lambda value: value == "completed",
569+
poll_interval_seconds=0.0001,
570+
max_wait_seconds=1.0,
571+
max_status_failures=5,
572+
)
573+
574+
assert timeout_attempts["count"] == 1
575+
576+
polling_attempts = {"count": 0}
577+
578+
async def get_status_polling_error() -> str:
579+
polling_attempts["count"] += 1
580+
raise HyperbrowserPollingError("upstream polling failure")
581+
582+
with pytest.raises(HyperbrowserPollingError, match="upstream polling failure"):
583+
await poll_until_terminal_status_async(
584+
operation_name="async poll polling-error passthrough",
585+
get_status=get_status_polling_error,
586+
is_terminal_status=lambda value: value == "completed",
587+
poll_interval_seconds=0.0001,
588+
max_wait_seconds=1.0,
589+
max_status_failures=5,
590+
)
591+
592+
assert polling_attempts["count"] == 1
593+
594+
asyncio.run(run())
595+
596+
484597
def test_poll_until_terminal_status_async_retries_server_errors():
485598
async def run() -> None:
486599
attempts = {"count": 0}
@@ -552,6 +665,43 @@ async def operation() -> str:
552665
asyncio.run(run())
553666

554667

668+
def test_retry_operation_async_does_not_retry_timeout_or_polling_errors():
669+
async def run() -> None:
670+
timeout_attempts = {"count": 0}
671+
672+
async def timeout_operation() -> str:
673+
timeout_attempts["count"] += 1
674+
raise HyperbrowserTimeoutError("timed out internally")
675+
676+
with pytest.raises(HyperbrowserTimeoutError, match="timed out internally"):
677+
await retry_operation_async(
678+
operation_name="async retry timeout passthrough",
679+
operation=timeout_operation,
680+
max_attempts=5,
681+
retry_delay_seconds=0.0001,
682+
)
683+
684+
assert timeout_attempts["count"] == 1
685+
686+
polling_attempts = {"count": 0}
687+
688+
async def polling_error_operation() -> str:
689+
polling_attempts["count"] += 1
690+
raise HyperbrowserPollingError("upstream polling failure")
691+
692+
with pytest.raises(HyperbrowserPollingError, match="upstream polling failure"):
693+
await retry_operation_async(
694+
operation_name="async retry polling-error passthrough",
695+
operation=polling_error_operation,
696+
max_attempts=5,
697+
retry_delay_seconds=0.0001,
698+
)
699+
700+
assert polling_attempts["count"] == 1
701+
702+
asyncio.run(run())
703+
704+
555705
def test_retry_operation_async_retries_server_errors():
556706
async def run() -> None:
557707
attempts = {"count": 0}
@@ -1125,6 +1275,28 @@ def get_next_page(page: int) -> dict:
11251275
assert attempts["count"] == 1
11261276

11271277

1278+
def test_collect_paginated_results_does_not_retry_timeout_errors():
1279+
attempts = {"count": 0}
1280+
1281+
def get_next_page(page: int) -> dict:
1282+
attempts["count"] += 1
1283+
raise HyperbrowserTimeoutError("timed out internally")
1284+
1285+
with pytest.raises(HyperbrowserTimeoutError, match="timed out internally"):
1286+
collect_paginated_results(
1287+
operation_name="sync paginated timeout passthrough",
1288+
get_next_page=get_next_page,
1289+
get_current_page_batch=lambda response: response["current"],
1290+
get_total_page_batches=lambda response: response["total"],
1291+
on_page_success=lambda response: None,
1292+
max_wait_seconds=1.0,
1293+
max_attempts=5,
1294+
retry_delay_seconds=0.0001,
1295+
)
1296+
1297+
assert attempts["count"] == 1
1298+
1299+
11281300
def test_collect_paginated_results_retries_server_errors():
11291301
attempts = {"count": 0}
11301302
collected = []
@@ -1366,6 +1538,31 @@ async def get_next_page(page: int) -> dict:
13661538
asyncio.run(run())
13671539

13681540

1541+
def test_collect_paginated_results_async_does_not_retry_timeout_errors():
1542+
async def run() -> None:
1543+
attempts = {"count": 0}
1544+
1545+
async def get_next_page(page: int) -> dict:
1546+
attempts["count"] += 1
1547+
raise HyperbrowserTimeoutError("timed out internally")
1548+
1549+
with pytest.raises(HyperbrowserTimeoutError, match="timed out internally"):
1550+
await collect_paginated_results_async(
1551+
operation_name="async paginated timeout passthrough",
1552+
get_next_page=get_next_page,
1553+
get_current_page_batch=lambda response: response["current"],
1554+
get_total_page_batches=lambda response: response["total"],
1555+
on_page_success=lambda response: None,
1556+
max_wait_seconds=1.0,
1557+
max_attempts=5,
1558+
retry_delay_seconds=0.0001,
1559+
)
1560+
1561+
assert attempts["count"] == 1
1562+
1563+
asyncio.run(run())
1564+
1565+
13691566
def test_collect_paginated_results_async_retries_server_errors():
13701567
async def run() -> None:
13711568
attempts = {"count": 0}

0 commit comments

Comments
 (0)