Каждая попытка получила полный timeout: считаем общий deadline через monotonic
Операция состоит из нескольких попыток. Каждой передают timeout=5, хотя вызывающий ожидает завершения всей операции за пять секунд. Три неудачные попытки растягивают ожидание примерно втрое, а паузы добавляют ещё время. Число в настройке осталось тем же, но его смысл изменился с общего бюджета на бюджет одной попытки.
Для общего ограничения вычисляют deadline один раз, а перед каждым новым действием получают remaining = deadline - now. В качестве часов нужен time.monotonic: их значение не уменьшается при корректировке системного календарного времени. Сами числа не являются датами; для логов времени суток по-прежнему используются другие часы.
Один бюджет на всю операцию
Покажем расчёт отдельно от сетевого клиента. Функция budget получает часы как аргумент, запоминает крайний срок и возвращает функцию проверки остатка. Такой небольшой компонент можно тестировать последовательностью значений, не замедляя тест реальными ожиданиями и не меняя системное время.
from collections.abc import Callable
from time import monotonic
def budget(seconds: float, clock: Callable[[], float] = monotonic):
deadline = clock() + seconds
def remaining() -> float:
value = deadline - clock()
if value <= 0:
raise TimeoutError("budget exhausted")
return value
return remaining
ticks = iter([100.0, 101.0, 104.0, 106.0])
remaining = budget(5, lambda: next(ticks))
assert remaining() == 4
assert remaining() == 1
try:
remaining()
except TimeoutError as error:
print(error)
else:
raise AssertionError("deadline must expire")
Передача остатка зависимости
Файл запускается на Python 3.12 и печатает budget exhausted. Первый запрос видит четыре оставшиеся секунды, следующий — одну, третий получает TimeoutError. В рабочем коде передайте очередной остаток в API зависимости, которое действительно умеет ограничивать ожидание. Если библиотека поддерживает только раздельные тайм-ауты фаз, выясните, какой из них соответствует вашему общему бюджету.
Один расчёт remaining не прерывает блокирующую функцию. Если перед вызовом осталось две секунды, а функция игнорирует timeout, Python не остановит её от наличия переменной deadline. Поэтому управляющий код и внешняя операция должны участвовать в одном протоколе. В async-коде общий timeout удобно ставить вокруг всей группы действий, но и там важны корректная отмена и очистка.
Учитывайте паузу между попытками. Не начинайте полный backoff, если после него не останется времени на полезную работу. Можно ограничить паузу остатком и затем снова проверить бюджет. Не передавайте отрицательный remaining дальше: некоторые API интерпретируют особые значения иначе, чем ожидается. Проверка <= 0 превращает истёкший бюджет в явный отказ на одном уровне.
Полезный тест приложения должен проверить, какие значения timeout фактически переданы в зависимость при нескольких попытках. Дополнительно проверьте истечение до первой попытки и успех до deadline. Так настройка «пять секунд на операцию» становится исполняемым контрактом. В логах записывайте выбранный бюджет и номер попытки, чтобы отличать долгую зависимость от многократного повторения с неверным расчётом времени.
Когда локальный пример работает, полезно разобрать соседние механизмы языка и их взаимодействие. В курсе «Python для профессионалов» есть практика по итераторам, генераторам, декораторам, asyncio, метапрограммированию и профилированию. Эти темы помогают понимать время жизни объектов, управление выполнением и причины поведения прикладной программы.
Посмотреть программу и практику курса.
Документация: официальный источник по теме.
Другие задачи по теме: Типы, тесты и поведение программы.
Продолжить на практике
- Python для профессионалов ↗
Python для профессионалов — курс для людей, стремящихся углубить свои знания языка. Вы изучите итераторы, генераторы, декораторы, современные возможности Python (3.8+), освоите метапрограммирование, асинхронность, внутреннее устройство интерпретатора, инструменты оптимизации и профилирования, а также получите уверенность в использовании продвинутых возможностей стандартной библиотеки.
