외부 명령을 exit 5로 끝냈는데 MediaMTX는 종료 코드 0을 기록했습니다. 자동 재시작을 끄자 이번에는 실패 종료 로그 자체가 사라졌습니다. 같은 명령의 실패가 재시작 ON에서는 성공 코드로, OFF에서는 기록 없는 종료로 보였습니다. 두 증상을 따라가니 실제 코드를 읽고도 반환하지 않는 한 분기가 있었습니다.
저는 실제 프로세스의 종료 상태와 서버가 받은 값을 대조하고, PR #6155의 수정 범위를 실패 결과를 기존 OnExit 콜백까지 전달하는 것으로 정했습니다. 종료 코드를 되찾는 데 실행·재시작 정책까지 바꿀 필요는 없었습니다. 조사·구현·실측·검토는 AI와 협업했고, 저는 공개할 원인과 검증 범위를 확인했습니다. 이 한 줄 수정은 2026년 8월 30일 병합돼 v1.21.0 릴리스에 포함됐습니다.
실패는 자식 프로세스에서 이미 발생하고 있었다
MediaMTX는 방송 시작이나 접속 같은 사건에서 외부 명령을 실행합니다. PokeClip처럼 훅을 파일 등록 등의 후속 처리와 연결하면, 명령의 성공 여부는 원인을 찾는 첫 정보가 됩니다. 이번에는 자식 프로세스가 이미 비영 코드로 끝났는데 그 결과가 서버까지 전달되는 동안 0으로 바뀌고 있었습니다.
non-Windows 구현은 실행 오류에서 *exec.ExitError를 찾고 ExitCode()를 호출했습니다. 그러나 반환하지 않아 아래의 기본값 0까지 제어 흐름이 내려갔습니다. 값을 읽는 코드가 있다는 것만 보고 정상이라고 판단하기 쉬운 형태입니다. 필요한 것은 계산식의 존재가 아니라 그 값이 호출자에게 도달하는지였습니다.
회귀를 도입한 커밋은 타입 단언을 errors.As로 바꾸는 과정에서 return을 빠뜨렸습니다. 이 형태는 v1.4.2부터 이어졌고, 이후 파일 이름이나 오류 변환 방식이 바뀌어도 누락이 남았습니다. Windows 구현은 이미 올바르게 반환하고 있었으므로 영향 범위를 모든 플랫폼으로 넓혀 쓰지 않았습니다.
같은 0이 재시작 설정에 따라 다르게 보였다
외부 명령의 결과는 기존 OnExit 보고 규칙을 거칩니다. 재시작 설정을 켠 경우에는 실패한 명령의 결과가 “code 0”으로 기록됐고, 끈 경우에는 잘못 전달된 0 때문에 실패 보고가 나타나지 않았습니다.
| 명령의 실제 종료 | 재시작 설정 | 수정 전 관측 | 수정 후 관측 |
|---|---|---|---|
exit 5 |
OFF | 실패 종료 로그 없음 | 종료 코드 5 보고 |
exit 5 |
ON | 종료 코드 0으로 기록 | 종료 코드 5 보고 |
| 정상 종료 | 기존 설정 | 기존 정책 적용 | 기존 정책 유지 |
PR의 최소 재현은 runOnInit에서 오류 종료하는 명령을 사용하는 방식입니다. 재시작을 켠 상태에서 같은 실패가 반복될 때 코드 0이 나오는지, 수정 후 실제 코드가 나오는지 비교했습니다. 재시작 간격을 바꾸는 방식으로 증상을 가리지 않았습니다.
그래서 재시작 ON에서 코드가 바뀌는 것만으로 검증을 끝내지 않았습니다. OFF에서는 기존에 없던 실패 보고가 나타나야 같은 반환값 수정이 두 호출 조건에서 제대로 해석됐다고 볼 수 있었습니다.
수정은 반환 경계에, 검증은 상위 동작까지
수정은 ExitError를 확인한 분기에서 ExitCode()를 즉시 반환하도록 하는 것입니다. 기존 훅의 시작·종료 정책, 재시작 여부와 간격은 유지했습니다. 회귀 테스트에서는 종료 코드 5를 만드는 명령으로 재시작 ON/OFF 두 조건을 모두 확인했습니다.
코드에서는 ee.ExitCode()를 읽던 자리에 return ee.ExitCode()를 두면 됩니다. 그러나 테스트의 끝은 그 반환문이 아니었습니다. 호출자가 5를 받고, ON과 OFF에서 각각 기존 규칙에 맞게 보고하는지까지가 수정의 계약이었습니다. Windows의 정상 반환 경로도 대조해 플랫폼별 영향 범위를 확인했습니다.
모든 훅의 실패가 이 수정 하나로 보이는 것은 아니다
이 PR은 이미 OnExit를 연결한 훅에 실제 실패 결과를 복구합니다. 콜백 자체가 없는 다른 호출부까지 해결한 것은 아닙니다. 이후 PR #6259에서는 외부 명령을 생성하는 13곳 중 콜백이 없는 7곳을 별도 문제로 다뤘습니다. 반환값과 호출부의 배선을 분리해 봐야 관측 공백의 전체 범위를 설명할 수 있습니다.
또 시그널로 종료된 프로세스는 Go의 종료 코드 의미에 따라 -1로 보고될 수 있습니다. MediaMTX가 직접 중지시키는 경로는 기존처럼 OnExit에 도달하기 전에 처리됩니다. 단순히 “0이 아니면 어떤 경우든 같은 실패 로그를 낸다”는 새로운 정책을 도입한 것이 아닙니다.
릴리스에 반영된 결과는 단순합니다. 5로 끝난 명령이 이제 서버에도 5로 전달됩니다. 이를 확인하려면 로그 문구보다 앞에 있는 프로세스 결과와 반환 분기, 콜백의 보고 조건까지 따라가야 했습니다. 수정은 한 줄이었지만, 그 한 줄이 상위 동작에서 어떤 차이를 만드는지 ON/OFF 두 조건으로 남긴 것이 회귀 검증의 핵심이었습니다.
이 글은 PokeClip·MediaMTX 기술 기록 모음의 일부입니다. 포트폴리오 전체본 41쪽에서 연결된 사례도 함께 볼 수 있습니다.