Bencher Runner Protocol


Бинарный файл runner и API-сервер обмениваются данными через одно WebSocket-соединение. В этом справочнике описан этот протокол: передаваемые сообщения, жизненный цикл Job, который они приводят в движение, а также то, как тайм-ауты и переподключение не дают Job застрять. Это подробное дополнение к руководству по Self-Hosted Runner.

Вам не нужно знать ничего из этого, чтобы управлять Runner с помощью runner up; это приведено для прозрачности и для всех, кто создаёт инструментарий вокруг API.


Соединение

Runner поддерживает одно WebSocket-соединение с API-сервером на протяжении всего своего жизненного цикла. Одно и то же соединение обрабатывает как назначение Job, так и выполнение Job, и остаётся открытым между множеством Job, избегая переподключения и рукопожатия для каждой из них.

  • Эндпоинт: /v0/runners/{runner}/channel
  • Аутентификация: ключ Runner отправляется как заголовок Authorization: Bearer bencher_runner_<key> при установлении соединения.
  • Размер сообщения: каждое сообщение ограничено лимитом сервера request_body_max_bytes (применяется как к максимальному размеру сообщения, так и к размеру кадра). Сообщение, превышающее этот лимит, например полезная нагрузка completed, несущая большие stdout, stderr или выходные файлы, отклоняется на уровне протокола WebSocket.

Каждое сообщение — это JSON-объект с полем event, которое определяет его тип.


Сообщения Runner

Сообщения, отправляемые от Runner к серверу.

Event Описание Полезная нагрузка
ready Runner простаивает и запрашивает Job Необязательные poll_timeout (1-900 с) и метаданные runner (os, arch, version, необязательный channel обновления и необязательная checksum бинарного файла)
running Настройка Job завершена, бенчмарк запускается Нет
heartbeat Периодический сигнал жизнеспособности (примерно раз в секунду) Нет
completed Бенчмарк успешно завершён job (UUID Job) и results (вывод по каждой итерации)
failed Бенчмарк завершился с ошибкой job (UUID Job), results и error
canceled Подтверждает отмену от сервера job (UUID Job)

Сообщения сервера

Сообщения, отправляемые от сервера к Runner.

Event Описание Полезная нагрузка
ack Подтверждает полученное сообщение Необязательный job (UUID Job)
job Назначает взятую Job для Runner Взятая Job: её Spec, конфигурация Job и краткоживущий OCI pull-токен
no_job Тайм-аут опроса истёк, доступной Job нет Нет
cancel Job отменена или истёк её тайм-аут; остановить выполнение Нет
update Runner должен самообновиться до новой версии version, url (URL загрузки) и checksum (SHA-256)

OCI pull-токен в сообщении job генерируется при взятии Job и никогда не хранится. Он ограничен единственным проектом, которому принадлежит Job, доступен только для загрузки и краткоживущий, поэтому скомпрометированный Runner может загружать Image только для проекта той Job, которую он взял.

На канале обновлений stable сообщение update отправляется, когда версия Runner отличается от версии сервера. На канале обновлений canary поле version равно canary, а сообщение update отправляется, когда сообщённая самим Runner контрольная сумма бинарного файла отличается от опубликованной скользящей canary-сборки.


Поток соединения

После подключения Runner входит в цикл опроса в режиме простоя, отправляя ready, пока сервер не назначит job (или не вернёт no_job, когда истечёт тайм-аут опроса, или update, когда доступна новая версия). Получив Job, Runner отправляет running, передаёт сообщения heartbeat, пока выполняется бенчмарк, и завершает терминальным сообщением completed или failed. Сервер подтверждает каждое сообщение через ack, а соединение остаётся открытым, поэтому Runner возвращается в цикл простоя для следующей Job.

Если Job отменена, сервер отвечает на heartbeat сообщением cancel. Runner останавливает бенчмарк и отвечает canceled, что сервер подтверждает.

API ServerRunnerAPI ServerRunneralt[Job available][Poll timeout][Update available]loop[Idle / polling]loop[Benchmark executes]Connect with runner keyConnectedready (os, arch, version)job (Spec, config, OCI token)no_jobupdate (version, url, checksum)runningackheartbeatack (or cancel)completed (job, results)ack

Жизненный цикл Job

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

Из В Триггер
pending claimed Runner берёт Job
pending canceled Пользователь отменяет Job
claimed running Runner отправляет running
claimed failed Runner отправляет failed, или истекает тайм-аут heartbeat
claimed canceled Пользователь отменяет Job
running completed Runner отправляет completed
running failed Runner отправляет failed, или истекает тайм-аут heartbeat
running canceled Пользователь отменяет Job, или превышен жёсткий тайм-аут Job
completed processed Сервер успешно обрабатывает результаты
failed completed Runner повторно отправляет completed, переопределяя сбой по тайм-ауту heartbeat

processed и canceled являются терминальными. completed и failed являются квази-терминальными: completed переходит в processed, как только результаты разобраны, а failed переходит в completed, если Runner повторно отправляет completed. Каждый переход использует фильтр по статусу в своём обновлении базы данных, поэтому Job, изменённая конкурентно, перечитывается, а не перезаписывается.

runner claims

user cancels

running

failed / timeout

user cancels

completed

failed / timeout

cancel / hard timeout

results recovered

results parsed

pending

claimed

canceled

running

failed

completed

processed


Тайм-ауты и восстановление

Три взаимодополняющих механизма гарантируют, что Job никогда не застрянет, даже если Runner упадёт или потеряет соединение.

Тайм-аут heartbeat

Пока соединение открыто, тайм-аут чтения обнаруживает Runner, который подключён, но молчит. Только корректные сообщения протокола сбрасывают таймер; некорректный JSON, кадры ping/pong и бинарные сообщения этого не делают. По тайм-ауту Job, которая выполнялась дольше своего тайм-аута плюс льготный период, помечается как canceled, а в остальных случаях помечается как failed (контакт с Runner был потерян).

Жёсткий тайм-аут Job

Сервер обеспечивает жёсткую максимальную продолжительность выполнения независимо от поведения Runner, поэтому ошибочный или скомпрометированный Runner не может работать бесконечно, отправляя heartbeat. Когда лимит (тайм-аут Job плюс льготный период) превышен, Job помечается как canceled, а Runner получает сообщение cancel.

Восстановление после разрыва соединения

Если соединение обрывается, пока Job ещё в процессе, сервер планирует проверку по истечении тайм-аута heartbeat. Если Runner переподключился и возобновил heartbeat, Job продолжается; в противном случае Job помечается как failed или canceled, если она превысила жёсткий тайм-аут. При запуске сервер также восстанавливает осиротевшие Job в состоянии claimed, переназначает тайм-ауты для Job в процессе и повторно обрабатывает Job в состоянии completed, чьи результаты были сохранены, но ещё не разобраны.

Переподключение и доставка результатов

Переподключение поддерживается и идемпотентно. Повторная отправка running для уже выполняющейся Job лишь обновляет её жизнеспособность, а повторная отправка терминального сообщения completed, failed или canceled всегда безопасна. Терминальные сообщения несут UUID Job и получают ack; если соединение обрывается до прихода ack, Runner сохраняет результат и повторно отправляет его при следующем соединении перед переходом в простой. Фактический результат completed от Runner может даже переопределить статус failed, полученный по тайм-ауту heartbeat.

Без автоматического повтора

Job в состоянии failed не повторяется автоматически. Неудачный бенчмарк — это сигнал, а не ошибка, которую нужно скрыть, поэтому повторный запуск остаётся за вами.


Вывод Job

Когда Runner отправляет completed или failed, полный вывод сохраняется в том же OCI-хранилище, что используется для контейнерных Image, по пути {project}/output/v0/jobs/{job}.

Сохранённый вывод содержит массив results по каждой итерации и, в случае сбоя, строку error. Каждая итерация записывает свой exit_code, stdout, stderr и карту любых собранных выходных файлов с их содержимым. После сохранения вывода сервер запускает адаптер harness бенчмарка по результатам, чтобы разобрать Metric и Alert в Report, переводя Job в состояние processed.

Вывод возвращается при запросе Job через API GET /v0/projects/{project}/jobs/{job}. Тот же лимит request_body_max_bytes, что ограничивает сообщения WebSocket, ограничивает размер вывода, который может доставить Runner.



Published: Fri, June 19, 2026 at 8:00:00 AM UTC | Last Updated: Tue, July 7, 2026 at 8:00:00 AM UTC