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

ИзВТриггер
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 | Last Updated: Tue, July 7, 2026 at 8:00:00 AM UTC