Мягкая отмена задач Cloud Mac CI без зависших процессов

Мягкая отмена задач Cloud Mac CI без зависших процессов

Самая сложная ситуация в очереди удалённой CI — не сбой, а отмена. Когда разработчик останавливает задачу через интерфейс, исполнитель обычно сначала отправляет сценарию сигнал TERM. Если сценарий его не перехватывает, запущенные процессы xcodebuild, тестирования или сбора журналов могут продолжить занимать рабочую область. Следующая задача столкнётся с файлами блокировки, занятым симулятором или неполным пакетом результатов. Внешне это выглядит как случайный сбой, хотя настоящая причина — некорректное завершение предыдущего запуска.

Сначала определите критерии завершённой отмены

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

  1. Основной процесс сборки получил сигнал завершения и прекратил работу.
  2. Сценарий зафиксировал фактическую причину выхода, а не записал её как обычный сбой сборки.
  3. Созданные журналы и xcresult сохранены.
  4. В рабочей области не осталось фоновых процессов и временных подключений, относящихся к этой задаче.

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

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

Перехватывайте TERM и отслеживайте основной процесс

Сценарий сборки должен явно сохранять PID процесса xcodebuild. При получении TERM или INT следует завершать только процессы, созданные текущей задачей, а не вызывать killall без ограничения области действия. В следующем каркасе Bash журналы и результаты сохраняются в отдельном каталоге запуска:

#!/bin/bash
set -u

run_id="${CI_RUN_ID:-manual-$(date +%s)}"
run_dir="$PWD/.ci-runs/$run_id"
result_path="$run_dir/TestResults.xcresult"
log_path="$run_dir/xcodebuild.log"
status_path="$run_dir/status.txt"
child_pid=""
cancelled=0

mkdir -p "$run_dir"

on_cancel() {
  cancelled=1
  if [[ -n "$child_pid" ]] && kill -0 "$child_pid" 2>/dev/null; then
    kill -TERM "$child_pid" 2>/dev/null || true
  fi
}

trap on_cancel TERM INT

xcodebuild \
  -workspace Example.xcworkspace \
  -scheme Example \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -resultBundlePath "$result_path" \
  test >"$log_path" 2>&1 &

child_pid=$!
wait "$child_pid"
build_status=$?

if [[ "$cancelled" -eq 1 ]]; then
  printf 'cancelled\n' >"$status_path"
  exit 130
fi

printf 'finished:%s\n' "$build_status" >"$status_path"
exit "$build_status"

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

Ограничьте время, отведённое процессу на завершение

Некоторые тесты могут не реагировать на TERM. Рабочий сценарий может после отправки сигнала проверять PID раз в секунду и ждать, например, 20 секунд, а по истечении этого срока отправить KILL. Льготный период должен быть ограничен, иначе отменённая задача навсегда займёт исполнитель. Но сокращать ожидание до одной-двух секунд тоже не следует: в этот момент xcodebuild может записывать пакет результатов.

Разделяйте сохранение диагностики и очистку кеша

Самая распространённая ошибка при очистке — выполнять rm -rf "$run_dir" непосредственно в trap cleanup EXIT. Каталог останется чистым, но вместе с ним исчезнут журналы, необходимые для выяснения причины. Надёжнее разделить содержимое на две категории:

Содержимое Действия после отмены
xcodebuild.log, xcresult, файл состояния Сохранить и архивировать
Временные каталоги, созданные текущей задачей Удалить после проверки пути
Общий кеш зависимостей Не удалять в обработчике отмены
Симулятор и производные данные Обрабатывать в соответствии со стратегией изоляции задач

Архивация журналов должна учитывать, что некоторые файлы могли ещё не появиться. Проверяйте наличие с помощью [[ -e "$result_path" ]], а не превращайте отсутствие пакета результатов во вторую ошибку. Если загрузка артефактов может занять много времени, задайте для неё отдельный тайм-аут и проследите, чтобы её сбой не перезаписывал исходный код выхода сборки.

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

safe_remove_run_dir() {
  local target="$1"
  local root="$PWD/.ci-tmp"

  [[ -n "$target" ]] || return 1
  [[ "$target" == "$root/"* ]] || return 1
  [[ -d "$target" ]] || return 0

  rm -rf -- "$target"
}

Ищите оставшиеся процессы вместо глобальной зачистки

После завершения задачи можно сначала записать снимок процессов:

ps -axo pid,ppid,state,etime,command >"$run_dir/processes-after.txt"
pgrep -P "$$" >"$run_dir/direct-children.txt" 2>/dev/null || true

Команда pgrep -P показывает только непосредственные дочерние процессы и не охватывает всех потомков. Поэтому надёжнее запускать каждый вспомогательный процесс из сценария и регистрировать его PID. Например, PID процессов пересылки журналов, тестовых агентов и перенаправления портов можно записывать в массив, а при очистке проверять их по одному. Не завершайте все одноимённые процессы на машине по имени: даже на выделенном физическом Mac mini могут одновременно работать долгоживущие процессы, запущенные разработчиком вручную.

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

Проверьте отмену с помощью трёх типов испытаний

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

Во время каждого испытания проверяйте следующее:

При длительной работе CI на облачных Mac от RentMini испытания отмены также следует включить в контрольный список приёмки после обновления исполнителя. Доступные узлы и конфигурации достаточно проверять в консоли; сам сценарий не должен зависеть от города, имени машины или фиксированного пути к рабочей области. Конечная цель не в том, чтобы каталоги выглядели идеально чистыми, а в том, чтобы каждое завершение имело чёткие границы и диагностические данные, а следующая сборка начиналась из однозначно определённого состояния.

Часто задаваемые вопросы

Нужно ли сразу отправлять kill -9 при отмене задачи CI?

Нет. Сначала отправьте TERM основному процессу сборки и задайте ограниченный период ожидания, чтобы сохранить журналы и результат. KILL допустим только после истечения этого срока.

Какие данные следует сохранить после отмены сборки?

Сохраните журнал xcodebuild, созданный пакет xcresult, файл итогового состояния и снимок процессов. Диагностические материалы нельзя безусловно удалять в обработчике выхода.

Облачный Mac по запросу

Подготовьте отдельный физический Mac mini к следующей сборке

Выберите конфигурацию M4, срок аренды и узел. Каждый заказ включает отдельное физическое устройство; актуальный статус доступности отображается в консоли в реальном времени.

Выбрать конфигурацию и арендовать