Skip to content
isdnetworks
Go back

다 끝났다는 신호가 먼저 왔다

장비 열세 대에서 수집 로그를 한꺼번에 받아 오는 스크립트를 만들었다. 한 대씩 돌리면 반나절이 걸린다. 열세 개를 백그라운드로 띄우고 wait 으로 전부 기다리게 짰다.

끝났다는 출력이 나왔다. 그런데 같은 시각에 세 대는 아직 파일을 쓰고 있었다.

Table of contents

Open Table of contents

wait이 기다리는 것은 자기 자식뿐이다

wait 은 현재 셸이 직접 띄운 자식 프로세스만 기다리고 매뉴얼에도 그렇게 적혀 있다.

for host in $HOSTS; do
  collect "$host" &
done
wait
echo "done"

이 형태 자체는 흔한 것이라 틀릴 데가 없어 보였는데 문제는 collect 안의 재시작 로직이었다.

collect 는 실패하면 자기를 다시 띄웠다. nohup 으로 새 프로세스를 만들고 자신은 종료한다.

새로 뜬 쪽은 그 셸의 자식이 아니라서 wait 이 세는 집합 밖으로 나간 것이다.

죽은 자식이 밖에서 되살아나면

그러면 이런 순서가 된다.

열세 개 중 세 개가 중간에 실패한다
그 셋이 각각 새 프로세스를 만들고 원래 프로세스는 끝난다
wait 은 자기 자식 열세 개가 전부 끝난 것을 확인한다
새로 뜬 셋은 아직 돌고 있는데 echo "done" 이 나온다

wait 은 거짓말을 하지 않았고 자기가 아는 자식 열세 개는 정말로 다 끝났다.

다만 내가 아는 작업과 셸이 아는 자식이 서로 다른 집합이 됐고 같은 말이 다른 것을 가리켰다.

종료 코드도 같은 이유로 못 믿는다

$? 가 0이라는 것도 마찬가지인데 실패한 셋의 원래 프로세스는 재시작을 띄운 뒤 정상 종료했다.

실패는 재시작 쪽으로 옮겨 갔는데 종료 코드는 성공으로 남았다. 로그에는 이렇게 찍혀 있었다.

[10:02] collect started: 13
[10:41] wrapper exit 0
[10:58] host-04 writing chunk 118

wrapper exit 0 뒤에 17분이나 더 파일이 쓰이고 있었다. 종료 코드 하나만 보고 다음 단계로 넘어갔으면 반쯤 받은 파일을 쓸 뻔했다.

조치 — 완료 판정의 두 근거

래퍼의 종료를 완료 판정에 쓰는 것을 그만두고 대신 두 가지를 따로 확인하게 했다.

첫째는 살아 있는 프로세스가 있는지다. pgrep -f collect 로 세면 셸의 자식이든 아니든 이름으로 잡히므로 재시작분도 포함된다.

둘째는 각 대상이 어디까지 갔는지이고 진행률을 파일에 남기게 하고 그것을 읽는다.

프로세스 있음                  → 도는 중
프로세스 없음 + 진행률 미달     → 죽음
프로세스 없음 + 진행률 끝       → 정상 종료

둘을 같이 봐야 도는 중과 끝남과 죽음이 갈리고 하나만으로는 셋 중 둘이 붙어 버린다.

pgrep 만 보면 죽은 것과 끝난 것이 같아 보인다. 진행률만 보면 도는 중인 것과 죽은 것이 붙는다.

재시작을 쓰면 갈라지는 것이 정상이다

처음에는 재시작 로직을 빼려고 했는데 그러면 wait 이 정확해지기 때문이다.

빼지 않았다. 열세 대 중 몇은 원래 응답이 불안정해서 재시작 없이 돌리면 매번 사람이 붙어 다시 실행해야 했다.

재시작은 그 문제를 실제로 줄이고 있어서 정확한 wait 을 얻으려고 버리면 손해가 더 컸다.

대신 래퍼가 아는 집합과 실제로 도는 집합이 갈라지는 것을 정상 동작으로 받아들였다. 완료 판정 쪽을 그 전제에 맞춰 고친 것이다.

재시작을 쓰는 구조에서는 두 집합이 갈라지는 것이 예외가 아니라 기본이다. 그 전제를 스크립트 주석에 적어 다음 사람이 같은 가정을 안 하게 했다.

정리


Share this post on:

Previous Post
전에 실패한 이력이 처리 방식을 정했다
Next Post
시험 환경이 살아 있는 동안 확인했다