Архитектура 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 для надёжности.»