Commit 759f400
committed
feat(FS-1988): add min_attempts and absolute_max_elapsed_time_ms to BackoffStrategy
Closes a short-circuit in retry_with_backoff{,_async} where a single slow
first attempt can exhaust max_elapsed_time before any retry fires. With
the partitioner config (max_elapsed_time=5min, CLIENT_TIMEOUT_MS=30min),
any chunk whose first attempt exceeds 5 minutes blew the retry budget
on attempt 1 -- zero retries on subsequent transient errors. Documented
in two customer-visible regressions in the platform partition path.
New fields on BackoffStrategy:
* min_attempts (default 0) -- minimum number of retry attempts that must
fire before max_elapsed_time is honored. Counts retries (not the
initial attempt). Default 0 preserves existing behavior.
* absolute_max_elapsed_time_ms (default None) -- cap on when a new retry
can START. Does NOT interrupt an in-flight func() call. Worst-case
wall-clock under this cap is absolute_max_elapsed_time_ms +
per_attempt_timeout. Default None preserves existing behavior.
Loop changes in both retry_with_backoff and retry_with_backoff_async:
* Post-attempt cap check honors min_attempts as a floor on the soft cap
and treats the hard cap as unconditional.
* Pre-sleep hard-cap check refuses to sleep into a doomed retry whose
projected start would exceed the hard cap.
* Post-sleep verification (belt-and-suspenders against late wakeups and
rounding drift in the projection).
* Helper extraction (_cap_hit_after_attempt, _raise_or_return_after_cap)
dedupes logic between sync and async paths.
Validation rejects min_attempts < 0, absolute_max_elapsed_time_ms <= 0,
and absolute_max_elapsed_time_ms below max_elapsed_time.
.genignore: ignore src/unstructured_client/utils/retries.py to preserve
these fields across Speakeasy regens. Documented merge procedure for
future template updates. Pushing these fields upstream to Speakeasy
templates is filed as a follow-up.
Tests:
* T1-T14 in unit/test_retries.py with a fake-clock harness that
monkeypatches time.time / time.sleep / asyncio.sleep / random.uniform.
Covers the v1 reproducer (slow first attempt + min_attempts floor),
floor-is-not-a-ceiling semantics, hard cap overrides floor, sleep
truncation, TemporaryError early-return through both caps, and
PermanentError short-circuit immunity.
* Validation tests for the new __init__ guards.
* New integration test
test_split_pdf_cache_tmp_data_chunk_request_stream_is_replay_safe
pins the body-replay invariant: chunks built from open file objects
(cache_tmp_data=True) must produce replay-safe httpx requests so SDK
retries actually deliver the original multipart payload. Iterates
request.stream twice directly to bypass request.read() caching.1 parent 5171475 commit 759f400
5 files changed
Lines changed: 840 additions & 25 deletions
File tree
- _test_unstructured_client
- integration
- unit
- src/unstructured_client/utils
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
1 | 8 | | |
2 | 9 | | |
3 | 10 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
813 | 813 | | |
814 | 814 | | |
815 | 815 | | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
| 866 | + | |
| 867 | + | |
| 868 | + | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
0 commit comments