codingjoe / codingjoe/threadmill
ExponentialBackoff overflows timedelta above attempt 46
- Ngôn ngữ chính
- Python
- Star
- 12
- Fork
- 1
- Merge trung bình
- 1 ngày 1 giờ
- Pull request đã merge (30 ngày)
- 10
Mô tả
`ExponentialBackoff.__call__` computes
```python
delay = min(self.base_delay * (self.factor**context.attempt), self.max_delay)
```
so the product is evaluated before `min()` clamps it. With the default shape (base delay 1 s, factor 2.0, max delay 1 h) the product exceeds the `timedelta` limit at attempt 47:
```
OverflowError: days=1628906115; must have magnitude <= 999999999
```
### Repro (threadmill 0.7.1)
```python
import datetime
from types import SimpleNamespace
from threadmill.retry import ExponentialBackoff
policy = ExponentialBackoff(
base_delay=datetime.timedelta(seconds=1),
max_delay=datetime.timedelta(hours=1),
factor=2.0,
max_retries=720,
)
def context(attempt):
error = SimpleNamespace(exception_class=ValueError)
return SimpleNamespace(attempt=attempt, task_result=SimpleNamespace(errors=[error]))
for attempt in range(1, 60):
try:
print(attempt, policy(context(attempt)))
except Exception as exc:
print(attempt, type(exc).__name__, exc)
break
```
Attempts 1 to 46 return a delay (2 s doubling to the 1 h cap at attempt 12), attempt 47 raises.
### Why it matters
`Executor.retry_delay` catches the exception, logs `Retry callback failed`, and returns `None`, so the backend acknowledges the result and the retry chain ends. The task looks like it exhausted its policy, but `max_retries` was never reached: a policy of 720 attempts really stops after 46 retries.
### Suggested fix
Clamp in seconds before building the `timedelta`, and derive the capped attempt count from `max_delay` so the exponential is never evaluated past the cap:
```python
seconds = min(
self.base_delay.total_seconds() * self.factor**context.attempt,
self.max_delay.total_seconds(),
)
return datetime.timedelta(seconds=seconds)
```
A plain `min()` on the two `timedelta` values does not help on its own, because the product still overflows before the comparison.
Found while bounding the spam scan retry budget in codingjoe/relay#230.
Hướng dẫn đóng góp
Hướng nghiên cứu
Bắt đầu tại threadmill.retry.ExponentialBackoff.__call__ và kiểm tra cách độ trễ của nó được tính trước khi áp dụng min(). Tái hiện vấn đề bằng vòng lặp lần thử được cung cấp, sau đó kiểm tra Executor.retry_delay để xác nhận rằng callback không còn lỗi ở số lần thử cao. Hoàn thành khi các lần thử đến max_retries vẫn được giới hạn ở max_delay thay vì kết thúc bằng overflow.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- python
- Lĩnh vực
- backend
- Loại issue
- Lỗi
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức phù hợp với người mới
- 78/100