|
1 | 1 | import asyncio |
2 | 2 | from concurrent.futures import CancelledError as ConcurrentCancelledError |
| 3 | +from concurrent.futures import BrokenExecutor as ConcurrentBrokenExecutor |
3 | 4 | import math |
4 | 5 | from fractions import Fraction |
5 | 6 |
|
@@ -221,6 +222,26 @@ def get_status() -> str: |
221 | 222 | assert attempts["count"] == 1 |
222 | 223 |
|
223 | 224 |
|
| 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 | + |
224 | 245 | def test_poll_until_terminal_status_retries_rate_limit_errors(): |
225 | 246 | attempts = {"count": 0} |
226 | 247 |
|
@@ -604,6 +625,24 @@ def operation() -> str: |
604 | 625 | assert attempts["count"] == 1 |
605 | 626 |
|
606 | 627 |
|
| 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 | + |
607 | 646 | def test_retry_operation_retries_server_errors(): |
608 | 647 | attempts = {"count": 0} |
609 | 648 |
|
@@ -931,6 +970,29 @@ async def get_status() -> str: |
931 | 970 | asyncio.run(run()) |
932 | 971 |
|
933 | 972 |
|
| 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 | + |
934 | 996 | def test_poll_until_terminal_status_async_retries_server_errors(): |
935 | 997 | async def run() -> None: |
936 | 998 | attempts = {"count": 0} |
@@ -1102,6 +1164,27 @@ async def operation() -> str: |
1102 | 1164 | asyncio.run(run()) |
1103 | 1165 |
|
1104 | 1166 |
|
| 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 | + |
1105 | 1188 | def test_retry_operation_async_retries_server_errors(): |
1106 | 1189 | async def run() -> None: |
1107 | 1190 | attempts = {"count": 0} |
@@ -2174,6 +2257,28 @@ def get_next_page(page: int) -> dict: |
2174 | 2257 | assert attempts["count"] == 1 |
2175 | 2258 |
|
2176 | 2259 |
|
| 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 | + |
2177 | 2282 | def test_collect_paginated_results_retries_server_errors(): |
2178 | 2283 | attempts = {"count": 0} |
2179 | 2284 | collected = [] |
@@ -2540,6 +2645,31 @@ async def get_next_page(page: int) -> dict: |
2540 | 2645 | asyncio.run(run()) |
2541 | 2646 |
|
2542 | 2647 |
|
| 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 | + |
2543 | 2673 | def test_collect_paginated_results_async_retries_server_errors(): |
2544 | 2674 | async def run() -> None: |
2545 | 2675 | attempts = {"count": 0} |
|
0 commit comments