Что происходит с async-операцией в Rust, когда её future уничтожают до завершения?
Уничтожение незавершённого future отменяет его дальнейшее выполнение: runtime больше не сможет опрашивать этот объект. При уничтожении вызывается обычный механизм Drop, поэтому его локальные значения освобождаются, но уже начатая внешняя операция не обязана автоматически остановиться или откатиться.
Отмена в Rust обычно кооперативная: future должен дойти до точки, в которой его можно безопасно уничтожить. Синхронный код, который уже выполняется внутри одного вызова poll, нельзя прервать уничтожением future.
Модель Future в Rust построена вокруг владения и ленивого выполнения. Вызов async-функции обычно только создаёт объект состояния; работа начинается, когда runtime начинает вызывать у него poll.
Такой дизайн позволяет не вводить обязательный механизм принудительного прерывания потока. Отмена естественно выражается через владение: если объект future больше никому не нужен, его можно уничтожить, а ресурсы освободятся по правилам Rust.
Задача может владеть буфером, сетевым запросом, регистрацией события в runtime, блокировкой или незавершённой транзакцией. Если future уничтожить между двумя точками ожидания, его состояние исчезнет, а код после следующего await выполнен не будет.
Риск возникает, когда разработчик принимает уничтожение future за гарантированное прерывание внешней работы. Например, закрытие локального объекта запроса может освободить соединение, но удалённый сервер уже мог принять команду и продолжить её выполнение.
Future хранит состояние async-функции, включая локальные переменные, необходимые для продолжения после await. Пока future существует, runtime может снова вызвать poll; после уничтожения объекта продолжать вычисление уже не из чего.
При уничтожении выполняется обычное освобождение вложенных значений. Поэтому RAII-очистка, закрытие владетельных дескрипторов и снятие локальных блокировок происходят согласно реализации типов, но это не превращается в универсальный протокол отмены внешней системы.
Отмена не является аппаратным прерыванием. Если poll выполняет длинный синхронный цикл без возврата управления, runtime не может безопасно уничтожить future посреди этого кода. После возврата из poll runtime может обнаружить отмену и уничтожить задачу или её future — точное поведение зависит от runtime.
Надёжный async-код должен явно учитывать отмену. Операции, которые могут быть прерваны после частичного прогресса, проектируют как cancellation-safe: повторный запуск не повреждает состояние, промежуточные данные сохраняются отдельно либо используется явная транзакция с подтверждением и откатом.
Если требуется сообщить об отмене нескольким операциям, часто применяют отдельный токен отмены или канал уведомления. Это отличается от уничтожения future: задача получает шанс выполнить собственную асинхронную очистку, тогда как Drop сам по себе синхронен и не может ожидать другой future.
Сервис обрабатывает платёж в async-задаче с тайм-аутом. При истечении тайм-аута future HTTP-запроса уничтожается, но платёжный провайдер мог уже принять запрос.
Вариант с одним только уничтожением future прост и освобождает локальные ресурсы, однако не даёт гарантии результата на стороне провайдера. Вариант с отдельным токеном отмены позволяет остановить локальную обработку, но также не отменяет уже принятую удалённую операцию. Вариант с идемпотентным ключом и последующей проверкой статуса сложнее, зато корректно обрабатывает неопределённый результат.
Практическое решение — использовать тайм-аут для ограничения ожидания, передавать провайдеру идемпотентный идентификатор и после отмены выполнять проверку статуса. Результат: локальная задача не блокируется бесконечно, повторная попытка не создаёт второй платёж, а отмена не ошибочно трактуется как доказательство отсутствия операции.
poll?Нет, само создание future не означает выполнение тела. Если runtime ни разу не вызвал poll, инструкции тела, включая создание его локальных переменных и побочные эффекты, не выполнятся. При уничтожении в таком состоянии освобождается только состояние самого future.
Нет, это зависит от реализации ожидаемого future. Он может снять регистрацию события, закрыть дескриптор или отправить сигнал отмены, но универсального требования остановить внешнюю работу нет. Особенно это важно для сетевых запросов, очередей и удалённых транзакций: локальное прекращение ожидания не отменяет уже доставленный запрос.
select должно быть cancellation-safe?В конструкциях выбора несколько future могут опрашиваться одновременно, а проигравшие ветви уничтожаются. Если проигравший future успел частично изменить внешнее состояние, но не оставил его в согласованном виде, повторная попытка может потерять данные или выполнить действие дважды.
Cancellation-safe операция либо не меняет состояние до момента, когда результат можно корректно вернуть, либо делает промежуточное изменение повторяемым и восстанавливаемым. Поэтому при проектировании future нужно учитывать не только успешное завершение и ошибку, но и уничтожение в произвольной точке ожидания.