После возврата Poll::Ready future можно снова передать исполнителю?
Нет. После возврата Poll::Ready исполнитель должен считать future завершённым и больше не вызывать для него poll. Повторный вызов не имеет гарантированного корректного результата: future может аварийно завершиться, зависнуть или нарушить свои внутренние инварианты.
Модель Future в Rust предназначена для представления отложенной операции, которую исполнитель продвигает вызовами poll. Это позволяет отделить описание асинхронной операции от конкретного async runtime и не удерживать поток в ожидании результата.
Чтобы такая модель была композируемой, результат Poll::Ready рассматривается как достижение терминального состояния. После него исполнитель должен удалить future из очереди активных задач, а не пытаться продвигать её дальше.
Исполнитель может ошибочно оставить завершённую future в очереди или повторно обработать устаревшее событие пробуждения. Если после этого снова вызвать poll, поведение не определяется обычной логикой ожидания: future уже сообщила, что результат готов.
Такой дефект способен привести к панике, бесконечному ожиданию, повторному освобождению ресурсов или повреждению логики пользовательского future. Особенно опасна ошибка в общем executor-коде: она затрагивает множество задач, хотя каждая из них по отдельности может быть корректной.
Контракт Future::poll допускает два промежуточных результата: Poll::Pending означает, что результат ещё не готов, а Poll::Ready(value) — что операция завершена. Получив Pending, исполнитель сохраняет future и ждёт уведомления через Waker; получив Ready, он извлекает future из активного цикла и использует её результат.
Документация трейта Future прямо предупреждает: после Poll::Ready клиентам не следует вызывать poll снова. Future не обязана поддерживать повторный вызов, возвращать тот же результат или сохранять какие-либо рабочие ресурсы.
Это не означает, что тип future обязан физически уничтожиться сразу. Исполнитель может передать завершённую future владельцу результата, освободить её позднее или выполнить завершающую очистку. Запрещённым является именно дальнейшее продвижение через poll, если конкретный API не устанавливает отдельный контракт.
Повторные вызовы Waker::wake не меняют правило. Пробуждение лишь просит executor снова проверить future, когда она находится в состоянии Pending; оно не должно возвращать в очередь уже завершённую future. Надёжная реализация executor обычно удаляет задачу после Ready и не допускает повторной постановки её в очередь.
В сервере используется собственный executor. После Ready задача удаляется из списка активных задач, но ранее запланированное событие пробуждения всё ещё содержит её идентификатор. При обработке этого события executor снова находит объект future и вызывает poll.
Рассматривались варианты:
poll, рассчитывая на добросовестную реализацию future; это просто скрывает ошибку и противоречит контракту Future;Ready переводить задачу в терминальное состояние, удалять её из активной очереди и игнорировать устаревшие уведомления; этот вариант соответствует контракту и сохраняет простую модель владения.Выбран третий вариант. В результате устаревшие события не приводят к повторному poll, а завершённые задачи корректно освобождаются после обработки результата.
1. Может ли future после Ready вернуть тот же результат при повторном poll?
Иногда конкретная реализация действительно способна это сделать, но такой эффект не является гарантией трейта Future. Исполнитель не вправе полагаться на поведение одной реализации: другая future может паниковать или содержать уже освобождённое внутреннее состояние.
2. Нужно ли отменять future, если executor получил Ready?
Нет, Ready — это завершение, а не отмена. Executor прекращает её опрашивать, но объект future может быть уничтожен позже обычным механизмом владения; при уничтожении сработает её Drop, если он реализован. Отмена относится к прекращению future до получения результата, например через удаление задачи в состоянии Pending.
3. Что делать с вызовом wake, который произошёл непосредственно перед Ready?
Такое уведомление может остаться в очереди, но оно не должно снова запускать poll завершённой future. Executor должен проверить, что задача ещё активна, либо использовать поколение, маркер состояния или другой механизм защиты от устаревших событий. Важно, что wake является сигналом о необходимости будущей проверки, а не разрешением нарушить терминальное состояние future.