pgrep -f와 pkill -f를 SSH로 사용했을 때, 대기 루프가 자기 자신을 35시간 동안 기다렸습니다
운영
10월 31일, SSH로 작은 루프를 시작했습니다. 다른 머신에서 번역 작업이 끝날 때까지 기다렸다가 후속 작업을 실행하는 루프였습니다. 번역 작업은 그날 밤에 끝났습니다. 이틀 후 다시 확인했을 때, 루프는 35시간 동안 대기 중이었고 후속 작업은 한 번도 실행되지 않았습니다.
루프의 정체
ssh build-host 'while pgrep -f "i18n_job.py example.com" >/dev/null; do sleep 30; done; ./follow-up.sh'
pgrep -f는 모든 프로세스의 전체 명령줄에서 패턴을 검색합니다. SSH는 따옴표로 묶은 명령을 bash -c 'while pgrep -f "i18n_job.py example.com" …' 형태로 실행하는데, 이 명령줄에 패턴이 포함되어 있습니다. pgrep는 자신의 프로세스는 결과에서 제외하지만, 이를 실행한 셸은 제외하지 않습니다. 따라서 루프가 살아 있는 한, 자기 자신과 일치하는 결과를 찾습니다.
형제 문제: pkill -f가 실행 중인 셸을 종료시킴
pkill -f에서도 같은 일이 발생하며, 결과는 더 심각합니다:
ssh build-host 'pkill -f "worker.py --queue a"; nohup python3 worker.py --queue a &'
셸의 명령줄에 패턴이 포함되어 있으므로 pkill이 셸을 종료시킵니다. 워커는 중지되고 다시 시작되지 않으며, SSH는 오류 메시지 없이 종료 코드 255로 종료됩니다. 마치 따옴표 처리 문제처럼 보이며, 처음에는 그렇게 판단했습니다.
9월부터 10월 말까지 둘 중 하나에 최소 열두 번 이상 빠졌습니다. 실행되지 않은 재시작, 3시간 30분 동안 검사를 붙잡아 둔 루프, 50분 동안 번역 큐를 붙잡아 둔 다른 루프, 그리고 이 사례가 있었습니다.
현재의 해결책
- 패턴에 대괄호 사용:
pgrep -f "[i]18n_job.py example.com". 정규식에서[i]는 문자 i와 일치하므로 작업은 여전히 일치합니다. 셸의 명령줄에는 문자[i]가 포함되지만, 이 정규식은 이를 일치시키지 않습니다. 단, 단순 패턴이 해당 줄의 다른 곳에 없어야 합니다. 같은 줄 뒤에 있는 재시작 명령이 다시 일치할 수 있습니다. - 검색, 중지, 시작을 별도의 SSH 호출로 분리합니다. 첫 번째 호출에서
pgrep -af로 프로세스를 나열하고, 두 번째 호출에서 출력된 PID를 종료하며, 세 번째 호출에서 새 프로세스를 시작합니다. - 패턴이 아닌 PID로 대기합니다:
tail --pid=1234 -f /dev/null또는while kill -0 1234; do sleep 30; done. - 더 나은 방법은 systemd가 작업을 담당하게 하는 것입니다. 유닛으로 실행하고
systemctl is-active로 확인하거나,systemd-run --wait로 시작하세요. 실수할 패턴이 없습니다. - 모든 대기는 마감 시간(
timeout 3h)을 가지며, 언제 포기하는지 알려줍니다. 마감 시간이 없는 루프는 조용히 실패합니다.
Factory Floor, 저희 기계를 구동하는 키트는 systemd 유닛을 통해 작동합니다: 명령줄로 프로세스를 찾지 않습니다. 저희를 괴롭혔던 루프들은 직접 입력했던 루프들이었습니다.