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ОписаниеПолезная нагрузка
readyRunner простаивает и запрашивает JobНеобязательные poll_timeout (1-900 с) и метаданные runner (os, arch, version)
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 нетНет
cancelJob отменена или истёк её тайм-аут; остановить выполнениеНет
updateRunner должен самообновиться до новой версииversion, url (URL загрузки) и checksum (SHA-256)

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


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

После подключения 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 проходит через фиксированный набор состояний по мере того, как её берут, выполняют и обрабатывают.

ИзВТриггер
pendingclaimedRunner берёт Job
pendingcanceledПользователь отменяет Job
claimedrunningRunner отправляет running
claimedfailedRunner отправляет failed, или истекает тайм-аут heartbeat
claimedcanceledПользователь отменяет Job
runningcompletedRunner отправляет completed
runningfailedRunner отправляет failed, или истекает тайм-аут heartbeat
runningcanceledПользователь отменяет Job, или превышен жёсткий тайм-аут Job
completedprocessedСервер успешно обрабатывает результаты
failedcompletedRunner повторно отправляет 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