MediaMTX의 runOnDisconnect에 존재하지 않는 /nonexistent/hook을 지정하고 RTSP 연결을 열었다 닫았습니다. 로그에는 command launched만 남았습니다. 실제로는 프로그램이 시작조차 못했으므로, 자식 프로그램의 stderr에서도 파일이 없다는 이유를 얻을 수 없었습니다. 실행을 요청한 부모 서버가 그 실패를 받아야 했습니다.
저는 외부 명령 생성 13곳을 대조해 실패 콜백이 없는 7곳을 찾고, PR #6259에서 기존 보고 방식을 연결했습니다. 7종 훅에서 통제한 19개 실행 사건의 실패 보고가 0건에서 19건으로 바뀌었습니다. 아래에서 종류 수와 사건 수, 결과를 확인한 조건을 나눠 설명합니다. PR은 2026년 10월 15일 확인 기준 OPEN이므로 병합·릴리스 성과와는 구분합니다.
종료 코드를 고치는 것과 콜백을 연결하는 것은 달랐다
앞서 PR #6155에서는 실제 오류 종료 코드가 0으로 덮이는 문제를 고쳤습니다. 하지만 그 값이 올바르게 반환돼도 호출부가 결과를 받는 콜백을 등록하지 않았다면 로그에는 나타나지 않습니다. 반환값의 정확성과 보고 경로의 존재를 따로 확인해야 했습니다.
외부 명령을 생성하는 13곳을 대조하니 6곳은 OnExit를 등록하고 있었고 7곳은 nil로 남아 있었습니다. 이 7곳의 생성 로그는 launched이고 기존 보고 그룹은 started라는 표현도 사용하고 있어, 단순 로그 검색만으로 두 집단을 같은 상태로 읽지 않아야 했습니다.
누락된 그룹에는 접속 해제, 읽기 종료, 수요 종료, 가용성 종료, 오프라인 전환, 녹화 조각 생성·완료 훅이 포함됐습니다. 실행 파일이 없으면 상속될 stderr를 쓸 자식 프로세스가 만들어지지 않습니다. 로그 목적지 설정을 더 추가하는 것보다 부모의 exec 실패를 받는 경계를 연결하는 것이 우선이었습니다.
새로운 실행 정책 대신 기존 보고 규칙을 사용했다
수정 대상은 runOnDisconnect, runOnUnread, runOnUnDemand, runOnUnavailable, runOnOffline, runOnRecordSegmentCreate, runOnRecordSegmentComplete의 7종입니다. 기존 6곳이 쓰던 로그 수준과 형식을 따라 OnExit를 전달했습니다.
externalcmd의 nil 기본값을 전역으로 바꾸지는 않았습니다. 보고를 원하지 않는 사용처까지 강제로 로깅하게 만드는 대신, 해당 호출부에 필요한 책임을 명시하는 방향입니다. 실행·재시작·종료 정책도 변경하지 않았습니다. 제 판단은 관측 공백을 복구 기능이나 새로운 훅 생명주기 정책과 섞지 않는 것이었습니다. 조사·구현·실측·리뷰에는 AI 협업이 있었습니다.
| 범위 | 바꾼 것 | 유지한 것 |
|---|---|---|
| 7종 호출부 | 기존 형식의 실패 콜백 연결 | command launched 기록 |
| 외부 명령 구현 | 변경 없음 | nil 기본값·실행 규칙 |
| 정상 종료 | 별도 오류 보고 없음 | 기존의 조용한 동작 |
| 실패 종료 | 실패 원인 보고 | 자동 복구·재시도 정책 추가 없음 |
도입의 접속 해제 재현에 이 변경을 적용하자, launched 다음에 fork/exec의 파일 없음 원인이 이어졌습니다. 이 경로의 전체 설정과 재현 명령은 공개 PR에 남겼습니다. 다음 확인은 나머지 훅에서도 같은 실패가 빠짐없이 보고되는지였습니다.
7종 배선과 19개 실행 사건을 따로 셌다
2026년 9월 7일 실측에서는 존재하지 않는 명령을 넣어 7종 훅을 실행했습니다. 이 프로브가 만든 실행 사건은 총 19개입니다. 운영에서 겪은 장애를 센 것이 아니라, 실행 시도 한 건마다 대응하는 실패 이유가 남는지 대조한 것입니다.
| 7종 프로브의 통제 사건 | 수정 전 | 수정 후 |
|---|---|---|
launched 기록 |
19건 | 19건 |
실패 이유를 포함한 exited 기록 |
0건 | 19건 |
| 수정 후 일반·race 실행의 DATA RACE | — | 0 |
| 수정 후 일반·race 실행의 panic | — | 0 |
테스트 위치도 실제 생성 지점을 따라 나눴습니다. hooks 패키지에 있는 5종은 그 패키지의 단위 테스트로, path.go에 직접 배선된 녹화 훅 2종은 서버 테스트로 다뤘습니다. 콜백의 동작만 확인하고 실제 호출부에서 연결이 빠지는 경우를 남기지 않으려는 범위입니다.
launched는 전후 모두 19건이므로 명령을 더 실행해서 보고 수가 늘어난 결과가 아닙니다. 같던 실행 시도에 실패 이유가 19건 대응하게 된 것입니다. 일반·race 실행에서 DATA RACE와 panic이 각각 0인 것도 확인했습니다. 명령 파일을 복구하거나 자동 재시도하는 기능은 이번 변경 범위에 넣지 않았습니다.
실패 로그의 양도 동작의 일부다
녹화 조각 훅이 계속 실패하면 조각마다 실패 로그 한 줄이 추가됩니다. 이 빈도는 기존 실행 시도 로그와 함께 읽어야 합니다. 정상 종료는 계속 조용하게 두되 실패 이유가 사라지지 않도록 하는 선택이므로, 운영자는 반복 실패의 로그량도 예상할 수 있어야 합니다.
명령이 시그널로 끝나는 경우의 종료 코드 -1 등 기존 의미도 유지합니다. 이미 연결된 6곳과 같은 콜백 규칙을 따르되, 누락된 7곳에서도 결과를 받도록 한 범위입니다. 상류 채택 여부는 2026년 10월 15일 확인 시점에 아직 열려 있었습니다.
처음의 launched는 실행에 성공했다는 기록이 아니었습니다. 이제 같은 시도 뒤에 fork/exec의 파일 없음 이유를 확인할 수 있습니다. 시작되지 않은 프로그램은 자기 오류를 기록할 수 없다는 제약을 따라가자, 더 많은 로그 설정이 아니라 부모 호출부의 실패 수신 책임을 고쳐야 한다는 결론에 도달했습니다.
이 글은 PokeClip·MediaMTX 기술 기록 모음의 일부입니다. 포트폴리오 전체본 41쪽에서 연결된 사례도 함께 볼 수 있습니다.