|
1 | 1 | import asyncio |
2 | | -from concurrent.futures import CancelledError as ConcurrentCancelledError |
3 | 2 | from concurrent.futures import BrokenExecutor as ConcurrentBrokenExecutor |
| 3 | +from concurrent.futures import CancelledError as ConcurrentCancelledError |
| 4 | +from concurrent.futures import InvalidStateError as ConcurrentInvalidStateError |
4 | 5 | import math |
5 | 6 | from fractions import Fraction |
6 | 7 |
|
@@ -287,6 +288,26 @@ def get_status() -> str: |
287 | 288 | assert attempts["count"] == 1 |
288 | 289 |
|
289 | 290 |
|
| 291 | +def test_poll_until_terminal_status_does_not_retry_invalid_state_errors(): |
| 292 | + attempts = {"count": 0} |
| 293 | + |
| 294 | + def get_status() -> str: |
| 295 | + attempts["count"] += 1 |
| 296 | + raise asyncio.InvalidStateError("invalid async state") |
| 297 | + |
| 298 | + with pytest.raises(asyncio.InvalidStateError, match="invalid async state"): |
| 299 | + poll_until_terminal_status( |
| 300 | + operation_name="sync poll invalid-state passthrough", |
| 301 | + get_status=get_status, |
| 302 | + is_terminal_status=lambda value: value == "completed", |
| 303 | + poll_interval_seconds=0.0001, |
| 304 | + max_wait_seconds=1.0, |
| 305 | + max_status_failures=5, |
| 306 | + ) |
| 307 | + |
| 308 | + assert attempts["count"] == 1 |
| 309 | + |
| 310 | + |
290 | 311 | def test_poll_until_terminal_status_does_not_retry_executor_shutdown_runtime_errors(): |
291 | 312 | attempts = {"count": 0} |
292 | 313 |
|
@@ -754,6 +775,24 @@ def operation() -> str: |
754 | 775 | assert attempts["count"] == 1 |
755 | 776 |
|
756 | 777 |
|
| 778 | +def test_retry_operation_does_not_retry_invalid_state_errors(): |
| 779 | + attempts = {"count": 0} |
| 780 | + |
| 781 | + def operation() -> str: |
| 782 | + attempts["count"] += 1 |
| 783 | + raise ConcurrentInvalidStateError("invalid executor state") |
| 784 | + |
| 785 | + with pytest.raises(ConcurrentInvalidStateError, match="invalid executor state"): |
| 786 | + retry_operation( |
| 787 | + operation_name="sync retry invalid-state passthrough", |
| 788 | + operation=operation, |
| 789 | + max_attempts=5, |
| 790 | + retry_delay_seconds=0.0001, |
| 791 | + ) |
| 792 | + |
| 793 | + assert attempts["count"] == 1 |
| 794 | + |
| 795 | + |
757 | 796 | def test_retry_operation_retries_server_errors(): |
758 | 797 | attempts = {"count": 0} |
759 | 798 |
|
@@ -1158,6 +1197,29 @@ async def get_status() -> str: |
1158 | 1197 | asyncio.run(run()) |
1159 | 1198 |
|
1160 | 1199 |
|
| 1200 | +def test_poll_until_terminal_status_async_does_not_retry_invalid_state_errors(): |
| 1201 | + async def run() -> None: |
| 1202 | + attempts = {"count": 0} |
| 1203 | + |
| 1204 | + async def get_status() -> str: |
| 1205 | + attempts["count"] += 1 |
| 1206 | + raise asyncio.InvalidStateError("invalid async state") |
| 1207 | + |
| 1208 | + with pytest.raises(asyncio.InvalidStateError, match="invalid async state"): |
| 1209 | + await poll_until_terminal_status_async( |
| 1210 | + operation_name="async poll invalid-state passthrough", |
| 1211 | + get_status=get_status, |
| 1212 | + is_terminal_status=lambda value: value == "completed", |
| 1213 | + poll_interval_seconds=0.0001, |
| 1214 | + max_wait_seconds=1.0, |
| 1215 | + max_status_failures=5, |
| 1216 | + ) |
| 1217 | + |
| 1218 | + assert attempts["count"] == 1 |
| 1219 | + |
| 1220 | + asyncio.run(run()) |
| 1221 | + |
| 1222 | + |
1161 | 1223 | def test_poll_until_terminal_status_async_retries_server_errors(): |
1162 | 1224 | async def run() -> None: |
1163 | 1225 | attempts = {"count": 0} |
@@ -1402,6 +1464,27 @@ async def operation() -> str: |
1402 | 1464 | asyncio.run(run()) |
1403 | 1465 |
|
1404 | 1466 |
|
| 1467 | +def test_retry_operation_async_does_not_retry_invalid_state_errors(): |
| 1468 | + async def run() -> None: |
| 1469 | + attempts = {"count": 0} |
| 1470 | + |
| 1471 | + async def operation() -> str: |
| 1472 | + attempts["count"] += 1 |
| 1473 | + raise ConcurrentInvalidStateError("invalid executor state") |
| 1474 | + |
| 1475 | + with pytest.raises(ConcurrentInvalidStateError, match="invalid executor state"): |
| 1476 | + await retry_operation_async( |
| 1477 | + operation_name="async retry invalid-state passthrough", |
| 1478 | + operation=operation, |
| 1479 | + max_attempts=5, |
| 1480 | + retry_delay_seconds=0.0001, |
| 1481 | + ) |
| 1482 | + |
| 1483 | + assert attempts["count"] == 1 |
| 1484 | + |
| 1485 | + asyncio.run(run()) |
| 1486 | + |
| 1487 | + |
1405 | 1488 | def test_retry_operation_async_does_not_retry_executor_shutdown_runtime_errors(): |
1406 | 1489 | async def run() -> None: |
1407 | 1490 | attempts = {"count": 0} |
@@ -2519,6 +2602,28 @@ def get_next_page(page: int) -> dict: |
2519 | 2602 | assert attempts["count"] == 1 |
2520 | 2603 |
|
2521 | 2604 |
|
| 2605 | +def test_collect_paginated_results_does_not_retry_invalid_state_errors(): |
| 2606 | + attempts = {"count": 0} |
| 2607 | + |
| 2608 | + def get_next_page(page: int) -> dict: |
| 2609 | + attempts["count"] += 1 |
| 2610 | + raise asyncio.InvalidStateError("invalid async state") |
| 2611 | + |
| 2612 | + with pytest.raises(asyncio.InvalidStateError, match="invalid async state"): |
| 2613 | + collect_paginated_results( |
| 2614 | + operation_name="sync paginated invalid-state passthrough", |
| 2615 | + get_next_page=get_next_page, |
| 2616 | + get_current_page_batch=lambda response: response["current"], |
| 2617 | + get_total_page_batches=lambda response: response["total"], |
| 2618 | + on_page_success=lambda response: None, |
| 2619 | + max_wait_seconds=1.0, |
| 2620 | + max_attempts=5, |
| 2621 | + retry_delay_seconds=0.0001, |
| 2622 | + ) |
| 2623 | + |
| 2624 | + assert attempts["count"] == 1 |
| 2625 | + |
| 2626 | + |
2522 | 2627 | def test_collect_paginated_results_does_not_retry_executor_shutdown_runtime_errors(): |
2523 | 2628 | attempts = {"count": 0} |
2524 | 2629 |
|
@@ -2934,6 +3039,31 @@ async def get_next_page(page: int) -> dict: |
2934 | 3039 | asyncio.run(run()) |
2935 | 3040 |
|
2936 | 3041 |
|
| 3042 | +def test_collect_paginated_results_async_does_not_retry_invalid_state_errors(): |
| 3043 | + async def run() -> None: |
| 3044 | + attempts = {"count": 0} |
| 3045 | + |
| 3046 | + async def get_next_page(page: int) -> dict: |
| 3047 | + attempts["count"] += 1 |
| 3048 | + raise ConcurrentInvalidStateError("invalid executor state") |
| 3049 | + |
| 3050 | + with pytest.raises(ConcurrentInvalidStateError, match="invalid executor state"): |
| 3051 | + await collect_paginated_results_async( |
| 3052 | + operation_name="async paginated invalid-state passthrough", |
| 3053 | + get_next_page=get_next_page, |
| 3054 | + get_current_page_batch=lambda response: response["current"], |
| 3055 | + get_total_page_batches=lambda response: response["total"], |
| 3056 | + on_page_success=lambda response: None, |
| 3057 | + max_wait_seconds=1.0, |
| 3058 | + max_attempts=5, |
| 3059 | + retry_delay_seconds=0.0001, |
| 3060 | + ) |
| 3061 | + |
| 3062 | + assert attempts["count"] == 1 |
| 3063 | + |
| 3064 | + asyncio.run(run()) |
| 3065 | + |
| 3066 | + |
2937 | 3067 | def test_collect_paginated_results_async_does_not_retry_executor_shutdown_runtime_errors(): |
2938 | 3068 | async def run() -> None: |
2939 | 3069 | attempts = {"count": 0} |
@@ -3209,6 +3339,35 @@ def fetch_result() -> dict: |
3209 | 3339 | assert fetch_attempts["count"] == 0 |
3210 | 3340 |
|
3211 | 3341 |
|
| 3342 | +def test_wait_for_job_result_does_not_retry_invalid_state_status_errors(): |
| 3343 | + status_attempts = {"count": 0} |
| 3344 | + fetch_attempts = {"count": 0} |
| 3345 | + |
| 3346 | + def get_status() -> str: |
| 3347 | + status_attempts["count"] += 1 |
| 3348 | + raise asyncio.InvalidStateError("invalid async state") |
| 3349 | + |
| 3350 | + def fetch_result() -> dict: |
| 3351 | + fetch_attempts["count"] += 1 |
| 3352 | + return {"ok": True} |
| 3353 | + |
| 3354 | + with pytest.raises(asyncio.InvalidStateError, match="invalid async state"): |
| 3355 | + wait_for_job_result( |
| 3356 | + operation_name="sync wait helper status invalid-state", |
| 3357 | + get_status=get_status, |
| 3358 | + is_terminal_status=lambda value: value == "completed", |
| 3359 | + fetch_result=fetch_result, |
| 3360 | + poll_interval_seconds=0.0001, |
| 3361 | + max_wait_seconds=1.0, |
| 3362 | + max_status_failures=5, |
| 3363 | + fetch_max_attempts=5, |
| 3364 | + fetch_retry_delay_seconds=0.0001, |
| 3365 | + ) |
| 3366 | + |
| 3367 | + assert status_attempts["count"] == 1 |
| 3368 | + assert fetch_attempts["count"] == 0 |
| 3369 | + |
| 3370 | + |
3212 | 3371 | def test_wait_for_job_result_does_not_retry_executor_shutdown_status_errors(): |
3213 | 3372 | status_attempts = {"count": 0} |
3214 | 3373 | fetch_attempts = {"count": 0} |
@@ -3472,6 +3631,29 @@ def fetch_result() -> dict: |
3472 | 3631 | assert fetch_attempts["count"] == 1 |
3473 | 3632 |
|
3474 | 3633 |
|
| 3634 | +def test_wait_for_job_result_does_not_retry_invalid_state_fetch_errors(): |
| 3635 | + fetch_attempts = {"count": 0} |
| 3636 | + |
| 3637 | + def fetch_result() -> dict: |
| 3638 | + fetch_attempts["count"] += 1 |
| 3639 | + raise ConcurrentInvalidStateError("invalid executor state") |
| 3640 | + |
| 3641 | + with pytest.raises(ConcurrentInvalidStateError, match="invalid executor state"): |
| 3642 | + wait_for_job_result( |
| 3643 | + operation_name="sync wait helper fetch invalid-state", |
| 3644 | + get_status=lambda: "completed", |
| 3645 | + is_terminal_status=lambda value: value == "completed", |
| 3646 | + fetch_result=fetch_result, |
| 3647 | + poll_interval_seconds=0.0001, |
| 3648 | + max_wait_seconds=1.0, |
| 3649 | + max_status_failures=5, |
| 3650 | + fetch_max_attempts=5, |
| 3651 | + fetch_retry_delay_seconds=0.0001, |
| 3652 | + ) |
| 3653 | + |
| 3654 | + assert fetch_attempts["count"] == 1 |
| 3655 | + |
| 3656 | + |
3475 | 3657 | def test_wait_for_job_result_does_not_retry_executor_shutdown_fetch_errors(): |
3476 | 3658 | fetch_attempts = {"count": 0} |
3477 | 3659 |
|
@@ -3819,6 +4001,38 @@ async def fetch_result() -> dict: |
3819 | 4001 | asyncio.run(run()) |
3820 | 4002 |
|
3821 | 4003 |
|
| 4004 | +def test_wait_for_job_result_async_does_not_retry_invalid_state_status_errors(): |
| 4005 | + async def run() -> None: |
| 4006 | + status_attempts = {"count": 0} |
| 4007 | + fetch_attempts = {"count": 0} |
| 4008 | + |
| 4009 | + async def get_status() -> str: |
| 4010 | + status_attempts["count"] += 1 |
| 4011 | + raise ConcurrentInvalidStateError("invalid executor state") |
| 4012 | + |
| 4013 | + async def fetch_result() -> dict: |
| 4014 | + fetch_attempts["count"] += 1 |
| 4015 | + return {"ok": True} |
| 4016 | + |
| 4017 | + with pytest.raises(ConcurrentInvalidStateError, match="invalid executor state"): |
| 4018 | + await wait_for_job_result_async( |
| 4019 | + operation_name="async wait helper status invalid-state", |
| 4020 | + get_status=get_status, |
| 4021 | + is_terminal_status=lambda value: value == "completed", |
| 4022 | + fetch_result=fetch_result, |
| 4023 | + poll_interval_seconds=0.0001, |
| 4024 | + max_wait_seconds=1.0, |
| 4025 | + max_status_failures=5, |
| 4026 | + fetch_max_attempts=5, |
| 4027 | + fetch_retry_delay_seconds=0.0001, |
| 4028 | + ) |
| 4029 | + |
| 4030 | + assert status_attempts["count"] == 1 |
| 4031 | + assert fetch_attempts["count"] == 0 |
| 4032 | + |
| 4033 | + asyncio.run(run()) |
| 4034 | + |
| 4035 | + |
3822 | 4036 | def test_wait_for_job_result_async_does_not_retry_executor_shutdown_status_errors(): |
3823 | 4037 | async def run() -> None: |
3824 | 4038 | status_attempts = {"count": 0} |
@@ -4106,6 +4320,32 @@ async def fetch_result() -> dict: |
4106 | 4320 | asyncio.run(run()) |
4107 | 4321 |
|
4108 | 4322 |
|
| 4323 | +def test_wait_for_job_result_async_does_not_retry_invalid_state_fetch_errors(): |
| 4324 | + async def run() -> None: |
| 4325 | + fetch_attempts = {"count": 0} |
| 4326 | + |
| 4327 | + async def fetch_result() -> dict: |
| 4328 | + fetch_attempts["count"] += 1 |
| 4329 | + raise asyncio.InvalidStateError("invalid async state") |
| 4330 | + |
| 4331 | + with pytest.raises(asyncio.InvalidStateError, match="invalid async state"): |
| 4332 | + await wait_for_job_result_async( |
| 4333 | + operation_name="async wait helper fetch invalid-state", |
| 4334 | + get_status=lambda: asyncio.sleep(0, result="completed"), |
| 4335 | + is_terminal_status=lambda value: value == "completed", |
| 4336 | + fetch_result=fetch_result, |
| 4337 | + poll_interval_seconds=0.0001, |
| 4338 | + max_wait_seconds=1.0, |
| 4339 | + max_status_failures=5, |
| 4340 | + fetch_max_attempts=5, |
| 4341 | + fetch_retry_delay_seconds=0.0001, |
| 4342 | + ) |
| 4343 | + |
| 4344 | + assert fetch_attempts["count"] == 1 |
| 4345 | + |
| 4346 | + asyncio.run(run()) |
| 4347 | + |
| 4348 | + |
4109 | 4349 | def test_wait_for_job_result_async_does_not_retry_executor_shutdown_fetch_errors(): |
4110 | 4350 | async def run() -> None: |
4111 | 4351 | fetch_attempts = {"count": 0} |
|
0 commit comments