Вам нужно отменять async-задачу во время записи в хранилище: какое свойство операции определяет, останется ли состояние корректным?
Это свойство называется безопасностью отмены (cancellation safety). Операция безопасна при отмене, если её можно прервать в любой точке ожидания без нарушения инвариантов, потери уже подтверждённых данных или некорректного поведения при повторной попытке.
Модель Future в Rust изначально предполагает, что вычисление может быть приостановлено, возобновлено или прекращено уничтожением future. Это позволяет runtime освобождать ресурсы и отменять ненужные задачи без отдельного механизма принудительной остановки потока.
Такая гибкость решает проблему управления большим числом долгоживущих операций ввода-вывода, но переносит ответственность за согласованность состояния на разработчика async-операции.
Отмена обычно происходит, когда future задачи уничтожается, в том числе во время ожидания .await. До ожидания операция могла уже изменить память процесса, отправить запрос во внешний сервис или записать часть данных в буфер.
Если после отмены состояние нельзя безопасно продолжить или повторить, возникают частичные записи, потерянные сообщения, дублирование платежей или рассинхронизация между локальным состоянием и внешней системой. Сам факт использования Drop для future не откатывает побочные эффекты, уже произошедшие вне future.
Безопасная при отмене операция должна сохранять инварианты на каждой точке, где управление может быть возвращено runtime. Практически это достигается атомарной фиксацией, транзакцией, идемпотентным протоколом, явной машиной состояний или сохранением промежуточного состояния так, чтобы повторный запуск был корректен.
Важно различать безопасность отмены и гарантию отмены. Уничтожение future прекращает дальнейшее продвижение именно этого вычисления, но библиотека ввода-вывода может уже передать запрос операционной системе или удалённому сервису. Поэтому отмена локального future не обязательно отменяет внешнюю операцию.
Также нельзя полагаться только на асинхронный Drop: деструктор Rust не может выполнить дополнительное .await. Если для завершения или отката требуется асинхронное действие, его нужно организовать явно через протокол, отдельную задачу восстановления или транзакционный API.
Компромисс состоит в том, что атомарность и идемпотентность требуют дополнительных идентификаторов операций, журналирования, блокировок или координации с внешней системой. Простое уничтожение задачи дешевле, но подходит только для операций, чьи частичные эффекты допустимы или автоматически безопасны.
Сервис принимает заказ и последовательно резервирует товар, списывает деньги и отправляет уведомление. Если задачу отменить после списания денег, но до записи результата в локальную базу, повторная попытка может списать деньги второй раз.
Вариант с прямым повторным выполнением прост, но не защищает от дублей. Транзакция базы данных хорошо защищает локальные изменения, однако не делает автоматически атомарными вызовы платёжного провайдера. Фоновая задача восстановления повышает надёжность, но требует журнала состояния и обработки сбоев.
Практичное решение — использовать уникальный идентификатор операции у платёжного провайдера, хранить состояние заказа и делать повторные запросы идемпотентными. Тогда отмена между двумя точками ожидания оставляет заказ в явно известном состоянии, а повторный запуск не создаёт второе списание.
1. Безопасно ли отменять операцию только потому, что она находится внутри .await?
Нет. .await — это точка, где future может вернуть управление runtime, но он не гарантирует откат уже выполненных побочных эффектов. Нужно знать контракт конкретной операции и состояние внешнего ресурса.
2. Решает ли повторный запуск задачи проблему небезопасной отмены?
Нет, повторный запуск может усугубить проблему, например создать дубликат записи или повторно списать деньги. Повторение безопасно только при идемпотентности, проверке уникального ключа или другом механизме, который связывает все попытки с одной логической операцией.
3. Чем безопасность отмены отличается от безопасности потоков?
Send и Sync описывают возможность безопасно передавать значения между потоками и совместно обращаться к ним. Безопасность отмены описывает сохранение корректности протокола при прекращении async-операции; тип может быть потокобезопасным, но сама последовательность его внешних действий всё равно может быть небезопасной при отмене.