Modern QA2026Архитектура Daemon: Модель процессов и жизненный цикл браузера
Join

Course01 Agent Skills for Browser Automation

Cutting-edge · Chapter 01

Архитектура Daemon: Модель процессов и жизненный цикл браузера

Updated Jul 2026

Два режима работы

Vibium работает в двух режимах, выбираемых для каждой команды:

Режим Daemon (по умолчанию)

Фоновый процесс поддерживает Chrome в рабочем состоянии между командами. Быстрый и с сохранением состояния.

# Первая команда: daemon запускается, Chrome стартует
vibe-check navigate https://example.com    # ~2 с (холодный старт)

# Последующие команды: используют существующий браузер
vibe-check text "h1"                       # ~100 мс (горячий)
vibe-check click "a"                       # ~200 мс (горячий)
vibe-check screenshot -o shot.png          # ~300 мс (горячий)

# Явное управление
vibe-check daemon status                   # Проверить статус работы
vibe-check daemon stop                     # Остановить daemon + браузер

Подходит для:

  • Интерактивных тестовых сессий
  • Многошаговых потоков, управляемых агентом
  • Разработки и отладки

Режим Oneshot

Новый браузер для каждой команды. Изолированный и без состояния.

# Каждая команда: запуск → выполнение → завершение
vibe-check navigate https://example.com --oneshot    # ~2 с
vibe-check text "h1" --oneshot                       # ~2 с (новый браузер!)

# Или через переменную окружения
VIBIUM_ONESHOT=1 vibe-check navigate https://example.com

Подходит для:

  • CI/CD-пайплайнов
  • Параллельного выполнения тестов
  • Тестов, требующих чистого состояния

Дерево процессов

Когда Vibium запускает сессию браузера:

clicker (Go binary, ~10MB)
  └── chromedriver
        └── Chrome for Testing (основной процесс браузера)
              ├── chrome_crashpad_handler (отчёты о сбоях)
              ├── GPU helper
              ├── Network helper
              ├── Storage helper
              ├── Renderer helper (один на вкладку/фрейм)
              └── ...другие помощники

Одна сессия порождает 8-12 процессов ОС. Это важно для:

  • Планирования ресурсов (память, CPU)
  • Очистки процессов (уничтожение одного не уничтожает все)
  • Сред CI (ограничения контейнеров)

Проблема зомби-процессов

Что идёт не так

Когда chromedriver уничтожается, его дочерние процессы (Chrome + помощники) переподчиняются PID 1 (launchd на macOS, init на Linux) до того, как код очистки может до них добраться. Они становятся сиротами:

До уничтожения:                После гибели chromedriver:
clicker                         clicker
  └── chromedriver              (исчез - уничтожен)
        └── Chrome                Chrome (parent = PID 1, осиротел!)
              └── GPU helper        └── GPU helper
              └── Renderer          └── Renderer

Почему нельзя убить Chrome первым?

Наивный подход — «просто убей Chrome перед chromedriver» — не работает, потому что:

  • Отправка DELETE /session в chromedriver может быть прервана сигналами
  • HTTP-запрос может истечь по тайм-ауту
  • Chromedriver может умереть раньше, чем Chrome полностью завершится
  • Состояния гонки между последовательностью очистки и управлением процессами ОС

Решение: Трёхфазная очистка

Реализовано в launcher.go:Close():

// Фаза 1: Вежливый запрос
// Отправить DELETE /session в chromedriver → просит Chrome корректно завершиться
// Best effort, может не сработать

// Фаза 2: Уничтожение дерева процессов
func killProcessTree(pid int) {
    descendants := getDescendants(pid)  // рекурсивный pgrep -P
    // Уничтожить самых глубоких дочерних первыми (обратный порядок)
    for i := len(descendants) - 1; i >= 0; i-- {
        syscall.Kill(descendants[i], syscall.SIGKILL)
    }
    syscall.Kill(pid, syscall.SIGKILL)
}

// Фаза 3: Подчистка сирот
// Найти процессы Chrome/chromedriver с родительским PID 1
// Они избежали уничтожения дерева — завершить их
killOrphanedChromeProcesses()

Что если сам Clicker умирает?

Сценарий Что происходит Очистка
Нормальный выход Close() выполняет все три фазы Автоматическая
Ctrl+C (SIGINT) Обработчик сигнала вызывает KillAll() Автоматическая
kill -9 (SIGKILL) Ничто не может перехватить это Сироты остаются до следующей сессии
Сбой системы Таблица процессов очищается ОС справляется

Для разработки make double-tap уничтожает все процессы Chrome for Testing и chromedriver:

# Ручная очистка во время разработки
make double-tap

# Отладка: проверка на сироты
pgrep -lf 'Chrome for Testing'
pgrep -lf chromedriver

# Проверка родительских PID (у сирот PPID = 1)
ps -o pid,ppid,comm -p $(pgrep -f 'Chrome for Testing')

Коммуникация Daemon

Daemon слушает на локальном WebSocket. CLI-команды подключаются к нему:

vibe-check click "button"
    │
    ▼
Daemon (бинарник clicker, постоянный)
    │
    ▼ WebSocket (BiDi)
Chrome (постоянный)

Если daemon не запущен, первая команда запускает его автоматически. Это поведение «автозапуска», которое делает инструмент бесшовным.

Влияние на тестовые фреймворки

Режим Daemon для тестовых наборов

# Начало тестового набора: обеспечить чистое состояние
vibe-check daemon stop 2>/dev/null
vibe-check daemon start

# Запуск тестов (все используют один браузер)
run_test "login_test"
run_test "dashboard_test"
run_test "checkout_test"

# Очистка
vibe-check daemon stop

Плюс: Быстро — нет перезапуска браузера между тестами Минус: Утечка состояния между тестами (куки, localStorage и т.д.)

Режим Oneshot для изолированных тестов

# Каждый тест получает свежий браузер
VIBIUM_ONESHOT=1 run_test "login_test"
VIBIUM_ONESHOT=1 run_test "dashboard_test"

Плюс: Идеальная изоляция — нет утечки состояния Минус: Медленно — ~2 с накладных расходов на тест для запуска браузера

Гибридный: Daemon + ручной сброс состояния

# Сохранить daemon для скорости, но сбрасывать состояние между тестами
vibe-check daemon start

for test in tests:
    vibe-check eval "localStorage.clear(); sessionStorage.clear()"
    vibe-check eval "document.cookie.split(';').forEach(c => document.cookie = c.trim().split('=')[0] + '=;expires=Thu, 01 Jan 1970')"
    vibe-check navigate "about:blank"
    run_test "$test"

vibe-check daemon stop

Плюс: Быстро + в основном изолировано Минус: Ручное управление состоянием, может пропустить серверное состояние сессии

Соображения для CI/CD

Безголовый режим

# Дисплей не нужен
vibe-check navigate https://example.com --headless

Контейнерные окружения

# Chrome нуждается в этих зависимостях на Linux
RUN apt-get install -y libgbm1 libnss3 libatk-bridge2.0-0 \
    libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
    libxfixes3 libxrandr2 libasound2

Параллельное выполнение

Каждый экземпляр daemon использует отдельный порт. Для параллельных тестов используйте режим oneshot или явно управляйте экземплярами daemon.

Тезис для собеседования

«Архитектура daemon Vibium — это прагматичный компромисс между скоростью и изоляцией. Daemon поддерживает Chrome в живом состоянии между командами — снижая задержку на команду с ~2 секунд до ~100 мс — в то время как режим oneshot обеспечивает изоляцию с чистым браузером для CI. Интересная техническая задача — очистка процессов: Chrome порождает 8-12 дочерних процессов, и уничтожение процесса драйвера делает их сиротами. Vibium решает это трёхфазной очисткой: запрос корректного завершения, рекурсивное уничтожение дерева процессов, затем зачистка сирот, которые избежали первых двух фаз. В нашем тестовом фреймворке мы используем режим daemon во время разработки для скорости и oneshot в CI для надёжности.»