Bencher Runner Protocol


runner 바이너리와 API 서버는 단일 WebSocket 연결을 통해 통신합니다. 이 참조 자료는 그 프로토콜을 설명합니다: 주고받는 메시지, 그 메시지가 이끄는 Job 수명 주기, 그리고 타임아웃과 재연결이 어떻게 Job이 멈추지 않도록 하는지를 다룹니다. 이는 Self-Hosted Runner 가이드의 심층 보충 자료입니다.

runner up으로 Runner를 운영하는 데 이 내용을 전혀 알 필요는 없습니다. 투명성을 위해, 그리고 API를 중심으로 도구를 만드는 사람을 위해 제공됩니다.


연결

Runner는 전체 수명 주기 동안 API 서버로 단일 WebSocket 연결을 유지합니다. 동일한 연결이 Job 할당과 Job 실행을 모두 처리하며, 여러 Job에 걸쳐 열린 상태로 유지되어 각 Job마다 재연결과 핸드셰이크를 피합니다.

  • 엔드포인트: /v0/runners/{runner}/channel
  • 인증: 연결이 수립될 때 Runner 키가 Authorization: Bearer bencher_runner_<key> 헤더로 전송됩니다.
  • 메시지 크기: 각 메시지는 서버의 request_body_max_bytes 제한(최대 메시지 크기와 프레임 크기 양쪽에 적용)으로 제한됩니다. 큰 stdout, stderr, 또는 출력 파일을 담은 completed 페이로드처럼 이 제한을 초과하는 메시지는 WebSocket 프로토콜 수준에서 거부됩니다.

모든 메시지는 그 유형을 식별하는 event 필드를 가진 JSON 객체입니다.


Runner 메시지

Runner가 서버로 보내는 메시지입니다.

Event설명페이로드
readyRunner가 유휴 상태이며 Job을 요청합니다선택적 poll_timeout(1-900초)과 runner 메타데이터(os, arch, version)
runningJob 설정이 완료되고 벤치마크가 시작됩니다없음
heartbeat주기적인 활성 신호(약 1초에 한 번)없음
completed벤치마크가 성공적으로 완료되었습니다job(Job UUID)과 results(반복별 출력)
failed벤치마크가 실패했습니다job(Job UUID), results, 그리고 error
canceled서버로부터의 취소를 확인합니다job(Job UUID)

서버 메시지

서버가 Runner로 보내는 메시지입니다.

Event설명페이로드
ack수신한 메시지를 확인합니다선택적 job(Job UUID)
job청구된 Job을 Runner에 할당합니다청구된 Job: 그 Spec, Job 설정, 그리고 짧은 수명의 OCI 풀(pull) 토큰
no_jobJob이 없는 상태로 폴 타임아웃이 만료되었습니다없음
cancelJob이 취소되었거나 타임아웃됨, 실행을 중지하세요없음
updateRunner가 새 버전으로 자체 업데이트해야 합니다version, url(다운로드 URL), 그리고 checksum(SHA-256)

job 메시지의 OCI 풀 토큰은 Job이 청구될 때 생성되며 저장되지 않습니다. 이 토큰은 Job이 속한 단일 프로젝트로 범위가 한정되고, 풀 전용이며, 짧은 수명을 가지므로, 손상된 Runner는 자신이 청구한 Job의 프로젝트에 대한 Image만 풀할 수 있습니다.


연결 흐름

연결 후, Runner는 유휴 폴링 루프에 진입하여, 서버가 job을 할당할 때까지 ready를 보냅니다 (또는 폴이 타임아웃되면 no_job을, 새 버전이 있으면 update를 반환합니다). Job을 받으면, Runner는 running을 보내고, 벤치마크가 실행되는 동안 heartbeat 메시지를 스트리밍하며, 종단(terminal) 메시지인 completed 또는 failed로 마무리합니다. 서버는 각 메시지를 ack로 확인하며, 연결은 열린 상태로 유지되어 Runner는 다음 Job을 위해 유휴 루프로 돌아갑니다.

Job이 취소되면, 서버는 heartbeatcancel로 응답합니다. 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은 청구, 실행, 처리되면서 고정된 상태 집합을 거칩니다.

FromTo트리거
pendingclaimedRunner가 Job을 청구합니다
pendingcanceled사용자가 Job을 취소합니다
claimedrunningRunner가 running을 보냅니다
claimedfailedRunner가 failed를 보내거나, heartbeat가 타임아웃됩니다
claimedcanceled사용자가 Job을 취소합니다
runningcompletedRunner가 completed를 보냅니다
runningfailedRunner가 failed를 보내거나, heartbeat가 타임아웃됩니다
runningcanceled사용자가 Job을 취소하거나, 강제(hard) Job 타임아웃을 초과합니다
completedprocessed서버가 결과를 성공적으로 처리합니다
failedcompletedRunner가 completed를 재전송하여 heartbeat 타임아웃 실패를 재정의합니다

processedcanceled는 종단(terminal) 상태입니다. completedfailed는 준종단(quasi-terminal) 상태입니다: completed는 결과가 파싱되면 processed로 전환되고, failed는 Runner가 completed를 재전송하면 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


타임아웃 및 복구

세 가지 상호 보완적인 메커니즘이, Runner가 충돌하거나 연결을 잃더라도 Job이 절대 멈추지 않도록 보장합니다.

Heartbeat 타임아웃

연결이 열려 있는 동안, 읽기 타임아웃은 연결되어 있지만 조용한 Runner를 감지합니다. 유효한 프로토콜 메시지만 타이머를 재설정합니다. 유효하지 않은 JSON, ping/pong 프레임, 바이너리 메시지는 재설정하지 않습니다. 타임아웃 시, 자신의 타임아웃에 유예 기간을 더한 시간보다 오래 실행된 Job은 canceled로 표시되고, 그렇지 않으면 failed로 표시됩니다(Runner와의 연결이 끊김).

강제(Hard) Job 타임아웃

서버는 Runner의 동작과 무관하게 강제 최대 실행 시간을 적용하므로, 버그가 있거나 손상된 Runner가 heartbeat를 보내어 무한정 실행할 수 없습니다. 제한(Job 타임아웃에 유예 기간을 더한 시간)을 초과하면, Job은 canceled로 표시되고 Runner는 cancel 메시지를 받습니다.

연결 끊김 복구

Job이 아직 진행 중인 상태에서 연결이 끊기면, 서버는 heartbeat 타임아웃 후에 확인을 예약합니다. Runner가 재연결하여 heartbeat를 재개했다면 Job은 계속됩니다. 그렇지 않으면 Job은 failed로, 강제 타임아웃을 초과했다면 canceled로 표시됩니다. 시작 시, 서버는 또한 고아가 된 claimed Job을 복구하고, 진행 중인 Job의 타임아웃을 다시 예약하며, 결과가 저장되었지만 아직 파싱되지 않은 completed Job을 다시 처리합니다.

재연결 및 결과 전달

재연결은 지원되며 멱등적(idempotent)입니다. 이미 실행 중인 Job에 대해 running을 재전송하면 그 활성 상태만 갱신되고, 종단(terminal) 메시지인 completed, failed, canceled를 재전송하는 것은 항상 안전합니다. 종단 메시지는 Job UUID를 담아 ack를 받습니다. ack가 도착하기 전에 연결이 끊기면, Runner는 결과를 저장하고 유휴 상태로 돌아가기 전에 다음 연결에서 다시 전송합니다. Runner의 실제 completed 결과는 heartbeat 타임아웃 failed 상태를 재정의할 수도 있습니다.

자동 재시도 없음

failed Job은 자동으로 재시도되지 않습니다. 실패한 벤치마크는 숨겨야 할 오류가 아니라 신호이므로, 다시 실행하는 것은 사용자에게 맡깁니다.


Job 출력

Runner가 completed 또는 failed를 보내면, 전체 출력은 컨테이너 Image에 사용되는 것과 동일한 OCI 스토리지 백엔드의 {project}/output/v0/jobs/{job} 경로에 저장됩니다.

저장된 출력은 반복별 results 배열을 포함하고, 실패 시에는 error 문자열을 포함합니다. 각 반복은 exit_code, stdout, stderr, 그리고 수집된 출력 파일과 그 내용의 맵을 기록합니다. 출력이 저장된 후, 서버는 결과에 대해 벤치마크 하니스 어댑터를 실행하여 Metric과 Alert를 Report로 파싱하고, Job을 processed로 전환합니다.

출력은 GET /v0/projects/{project}/jobs/{job} API로 Job을 조회할 때 반환됩니다. WebSocket 메시지를 제한하는 동일한 request_body_max_bytes 제한이 Runner가 전달할 수 있는 출력의 크기를 제한합니다.



Published: Fri, June 19, 2026 at 8:00:00 AM UTC