Программирование RustКонкурентность и asyncРазработчик Rust, специализирующийся на асинхронных сетевых сервисах

Во время ожидания сетевого события future возвращает Pending: каким образом Waker обеспечивает её повторную...

Во время ожидания сетевого события future возвращает Pending: каким образом Waker обеспечивает её повторную проверку после готовности события?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Waker связывает ожидающий future с механизмом планирования async runtime. Когда future возвращает Poll::Pending, она или используемая ею библиотека регистрирует переданный runtime waker во внешнем источнике события. После готовности события этот waker вызывается, runtime ставит задачу в очередь на повторный poll, а сама future проверяет состояние заново.

Вызов wake не продолжает future немедленно и не означает, что операция уже завершена. Он лишь сообщает исполнителю: задачу нужно снова опросить.

Исторический контекст

Асинхронный код должен ожидать медленные операции, не удерживая поток в блокирующем вызове. Для этого Rust разделяет описание операции, представленное Future, и механизм её исполнения, предоставляемый runtime.

Одного возвращения Pending недостаточно: исполнителю нужно знать, когда имеет смысл повторить проверку. Waker решает эту задачу без постоянного опроса всех незавершённых futures и без выделенного потока на каждую операцию.

Постановка проблемы

Пусть future ожидает сетевой пакет и при первом poll ещё не может завершиться. Если она просто вернёт Pending, но не зарегистрирует waker, runtime не получит надёжного сигнала о моменте готовности данных.

Постоянный активный опрос создаёт лишнюю нагрузку на процессор, а блокировка потока снижает масштабируемость. Ошибка в регистрации или вызове waker может привести к зависшей задаче, которая никогда не будет опрошена повторно.

Подробное решение

Во время poll runtime передаёт future контекст, содержащий текущий waker. Future передаёт его дальше драйверу ввода-вывода или сохраняет в состоянии операции, если именно она отвечает за ожидание события.

Если событие пока не готово, future возвращает Poll::Pending. Когда драйвер обнаруживает готовность сокета, таймера или другого ресурса, он вызывает wake у зарегистрированного waker. Обычно это приводит к постановке связанной async-задачи в очередь исполнителя.

При следующем poll future должна снова проверить источник события. Если данные уже доступны, она возвращает Poll::Ready; если нет, снова регистрирует актуальный waker и возвращает Pending.

Waker является сигналом, а не результатом операции: вызов wake может произойти раньше фактической готовности, повториться несколько раз или быть объединён runtime. Поэтому future обязана корректно обрабатывать лишние пробуждения и не полагаться на точное число вызовов wake.

Нельзя бездумно хранить старый waker: задача может быть перепланирована с другим waker. Реализация ожидания должна обновлять сохранённый waker, если он относится к другому экземпляру планировщика. Кроме того, после завершения или отмены операции зарегистрированный источник событий не должен продолжать использовать недействительное состояние future.

Ситуация из практики

Сервис обрабатывает тысячи TCP-соединений. Для каждого соединения future при отсутствии данных возвращает Pending, а сетевой драйвер будит только те задачи, чьи сокеты стали готовы.

Вариант с блокирующим чтением потребовал бы отдельный поток или блокирующий пул на множество соединений. Это проще для локальной логики, но увеличивает потребление потоков и ухудшает масштабирование.

Вариант с циклом постоянного опроса всех сокетов не блокирует потоки, но тратит процессор на проверки, когда событий нет. Выбранная схема с регистрацией waker и уведомлением драйверами событий позволяет ожидать готовность эффективно: runtime повторно опрашивает только потенциально активные задачи.

Что кандидаты часто упускают

1. Дополнительный вопрос: обязан ли вызов wake немедленно выполнить future?

Нет. wake только делает задачу доступной для последующего планирования. Runtime может поставить её в очередь, объединить несколько пробуждений или выполнить позднее на другом потоке. Само выполнение происходит при следующем вызове poll.

2. Дополнительный вопрос: почему после wake future всё равно должна заново проверить источник события?

Пробуждение не гарантирует, что ожидаемое условие всё ещё истинно к моменту poll. Событие могло быть потреблено другой задачей, готовность могла быть временной, а пробуждение — лишним. Поэтому корректный контракт future требует проверять фактическое состояние и возвращать Ready только при реальной готовности результата.

3. Дополнительный вопрос: что произойдёт, если future вернёт Pending, но не сохранит переданный waker?

Runtime не получит необходимого уведомления и может больше не вызвать poll для этой задачи. В результате future зависнет, хотя внешний ресурс впоследствии станет готов. Реализация должна зарегистрировать waker до возврата Pending, а при последующих опросах заменить устаревший waker актуальным.