Sandbox Lifecycle

Арендованная среда исполнения — не «запущена или остановлена», а машина состояний с двумя таймерами и счётчиком активности. Разница между состояниями измеряется деньгами, а ошибки в переходах обнаруживаются счётом в конце месяца или потерянной работой.

Четыре состояния и что каждое стоит

провижининг → активна → засыпает → спит
                 ↑            |
                 +————————————+  (восстановление)
Состояние Что происходит Стоимость
провижининг машина поднимается, ставятся зависимости тариф уже идёт
активна агент работает, команды выполняются полный тариф
засыпает снимается снимок, среда доделывает полный тариф
спит машина остановлена, снимок сохранён только хранение

Дорогое состояние одно — «активна». Два переходных коротки, но тарифицируются полностью, поэтому частые засыпания и пробуждения дороже, чем кажется: каждый цикл оплачивается дважды по полной ставке.

Два таймера, и свой у вас только один

Жёсткое истечение назначает поставщик — от часа до нескольких. Когда счётчик дошёл до нуля, машина убивается независимо от того, что на ней происходит. Продлить нельзя, договориться нельзя; можно только успеть или снять снимок заранее.

Окно неактивности назначаете вы: после N минут без активности среда усыпляет себя сама. Пять минут — разумное умолчание для агентных нагрузок; две минуты агрессивны (среда засыпает между ходами диалога), двадцать — расточительны.

Разница практическая: жёсткое истечение задаёт худший счёт, окно неактивности — типичный. Планировать надо оба.

Что считать активностью — здесь ломается чаще всего

Счётчик активности это одна отметка времени, обновляемая на «настоящем» событии. Весь вопрос в том, что считать настоящим:

Событие Активность?
сообщение пользователя да
выполненный вызов инструмента да
изменение файла, запуск процесса да
опрос состояния нет
проба при переподключении нет
проверка живости нет

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

Снимок сохраняет файлы, а не работу

Снимок замораживает состояние файловой системы: содержимое рабочего каталога, установленные пакеты, всё созданное агентом. Он не сохраняет запущенные процессы, открытые сетевые соединения и состояние в памяти.

Следствие для модели поведения агента: после восстановления файлы на месте, но сборка, шедшая в момент снимка, не продолжится — её придётся запускать заново. Это отличает снимок среды от возобновляемости графа (Durable Execution), где восстанавливается именно ход исполнения.

Три ловушки идемпотентности

Все три — про операцию, вызванную дважды там, где второй вызов не безобиден.

Снимок поверх снимка. Второй запрос приходит, пока первый ещё идёт. Без защиты получаются два снимка, дерущихся за одну машину, ошибка поставщика или частичный снимок, выглядящий целым. Лечится запоминанием обещания: второй вызов возвращает то же самое, что первый, и очищается по завершении.

Восстановление поверх работающей среды. Пользователь переподключается к живой сессии, а харнесс запускает восстановление. Теперь машин две: новая жжёт деньги, старая обслуживает трафик. Лечится порядком «сначала посмотри, потом создавай» — путь восстановления обязан начинаться с поиска активной среды.

Двойная остановка. stop вызывается таймером неактивности и пользователем, или при засыпании и при жёстком истечении. Второй вызов приходит в уже несуществующую среду и, в зависимости от поставщика, либо падает громко, либо портит состояние молча. Лечится флагом.

Пять отказов, каждый из которых стоил кому-то денег

Устаревший дескриптор после переподключения. Дескриптор пережил разрыв, сессия внутри него — нет. Команды уходят, ответом приходит мусор или зависание. Лечится дешёвой пробой перед использованием: выполнить заведомо предсказуемую команду и сверить вывод, а при расхождении восстановиться из снимка.

Устаревшие данные об истечении. Момент истечения, полученный при создании и закэшированный, устарел уже в момент записи. Хуже, если производное значение уходит в вызов поставщика: так создаётся среда, истёкшая при рождении. Правило: кэшировать срок можно для показа, но не для управляющих решений — перед решением он запрашивается заново.

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

Петля автовозобновления. Переподключение поднимает среду из снимка; подъём запускает проверку жизненного цикла; та не видит активности и решает снять снимок; снимок ведёт к засыпанию; засыпание — к следующему автовозобновлению. Два куска кода, каждый из которых по отдельности выглядит верным, вместе дают бесконечный цикл. Лечится правилом не сцеплять переходы автоматически: возобновление только при первом входе, дальнейшие подключения присоединяются к активной среде.

Расхождение состояний. Состояние живёт в трёх местах — у поставщика, в вашей базе, в кэше клиента, — и они разойдутся. Вопрос не в том, случится ли это, а в том, какому источнику верить: источник истины — API поставщика, остальное кэш. От выбора зависит, будет ошибка стоить денег или доверия.

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

Почему таймер не живёт в обычном коде

Логика цикла проста: раз в полминуты проверить, достаточно ли долго среда простаивает, и если да — снять снимок и остановить. В долгоживущем сервере это таймер, и на этом всё.

В бессерверной среде так не выйдет: функция завершается, рантайм убирает её процесс, таймер собирается сборщиком мусора. Проверка не выполняется никогда, среда продолжает работать, счёт продолжает расти — а код жизненного цикла лежит в процессе, которого больше нет. Даже если бы функция дожила до первой проверки, следующее развёртывание заменило бы её и потеряло состояние таймера.

Нужен механизм, умеющий спать через границу процесса и просыпаться на другой стороне, — долговечный рабочий процесс. Конкретная реализация зависит от платформы, переносим сам признак: если пауза дольше жизни процесса, она обязана жить вне процесса. Со стороны графа ту же задачу решает Durable Execution.

Связано с

  • Sandbox Abstraction — контракт со средой; здесь то, что происходит с ней во времени
  • Agent Sandboxing — чем изолировать, в отличие от того, как эксплуатировать
  • Durable Execution — возобновляемость со стороны графа исполнения, а не арендованной машины
  • Agent CostControl — состояние «активна» и есть та статья расходов, которой управляет окно неактивности
  • Agent Harness — обвязка, для которой среда одна из точек расширения