После уничтожения runtime Tokio что произойдёт с незавершённой async-задачей, созданной в нём?
При уничтожении Tokio runtime незавершённые async-задачи, созданные этим runtime, обычно немедленно удаляются и не получают гарантии завершения. Их локальные значения уничтожаются, но код после последней точки ожидания может не выполниться. Это поведение относится именно к Tokio; язык Rust сам по себе не задаёт единую политику завершения всех async runtime.
Асинхронные задачи отделены от обычных потоков: они управляются runtime, который хранит очереди задач, планирует их опрос и обеспечивает работу реактора ввода-вывода. Поэтому runtime должен определить, что делать с задачами при завершении собственного жизненного цикла.
Такой подход позволяет быстро завершать сервис или тестовый контекст без ожидания каждой фоновой задачи. Обратная сторона — простого запуска задачи недостаточно для гарантии выполнения; завершение должно быть явно согласовано с владельцем задачи.
Фоновая задача может выполнять запись, отправлять сообщение или освобождать внешний ресурс. Если уничтожить runtime раньше, чем задача достигнет Poll::Ready, Tokio удалит задачу, и ожидаемая операция может не завершиться.
Нельзя считать вызов spawn эквивалентом гарантированного фонового процесса. Если результат задачи не нужен, это не означает, что её работа переживёт runtime или завершится до выхода из функции.
Tokio хранит async-задачи внутри runtime. При его уничтожении незавершённые async-задачи удаляются: их future уничтожаются, поэтому выполняются обычные деструкторы уже существующих локальных значений, но выполнение future не возобновляется и не доводится до следующей точки завершения.
Минимальный пример показывает отсутствие гарантии вывода:
Даже первое сообщение не гарантировано: runtime может быть уничтожен до первого опроса задачи. Второе сообщение не гарантировано тем более, поскольку задача ожидает таймер и удаляется при завершении runtime.
Для корректного завершения следует сохранить JoinHandle и дождаться результата через await. Если задачу нужно отменить, отмену следует проектировать явно: проверить, безопасна ли операция в любой точке, выполнить откат или использовать идемпотентную запись.
Есть важное исключение по классу задач: операции, запущенные через spawn_blocking, не прекращаются простым abort, если уже начали выполняться. При завершении runtime Tokio может ждать такие операции; ограничение времени ожидания прекращает ожидание runtime, но не останавливает уже выполняющийся блокирующий код.
HTTP-сервис запускает задачу, которая асинхронно сохраняет аудит события, а затем сразу завершает runtime после отправки ответа. Возможны три подхода.
JoinHandle. Это минимально меняет код, но запись может быть потеряна при остановке процесса.Если запись критична, дополнительно используют надёжную очередь или транзакционный outbox. Тогда уничтожение runtime не считается механизмом доставки, а является лишь частью контролируемого жизненного цикла приложения.
Вопрос: Гарантирует ли JoinHandle выполнение задачи, если его сохранить, но не вызвать await?
Ответ: Нет. Само хранение дескриптора не заставляет задачу завершиться и не предотвращает уничтожение runtime. Гарантия появляется только при корректном ожидании задачи и сохранении runtime достаточно долго для этого ожидания.
Вопрос: Можно ли считать уничтожение future полноценной отменой операции во внешней системе?
Ответ: Нет. Уничтожение future прекращает дальнейшее выполнение Rust-кода этой future, но уже отправленный сетевой запрос, запись в драйвере или операция удалённого сервиса может продолжиться. Для корректной отмены нужны свойства конкретного API: поддержка отмены, идемпотентность, транзакция или компенсационная операция.
Вопрос: Чем завершение Tokio runtime отличается от вызова abort у отдельной задачи?
Ответ: abort адресно помечает конкретную async-задачу для прекращения и приводит к удалению её future при обработке отмены. Уничтожение runtime прекращает управление всеми его async-задачами сразу. В обоих случаях завершение задачи не означает откат внешних побочных эффектов, а для уже выполняющегося spawn_blocking-кода abort не останавливает синхронное выполнение.