Каждая попытка получила полный 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+), освоите метапрограммирование, асинхронность, внутреннее устройство интерпретатора, инструменты оптимизации и профилирования, а также получите уверенность в использовании продвинутых возможностей стандартной библиотеки.