trouble shooting

멀티 프로세싱 프로그램에서 자식이 에러날 경우 부모 죽이기(pkill)

식피두 2021. 1. 5. 16:49

드론 관련 과제를 진행하면서 개발하고 있는

객체 트래킹 시스템 자체가 매우 복잡해졌다.

 

이 시스템은 채점 시스템에 업로드 되어 실행되고

결과를 내도록 작성되어있다.

 

마스터, 워커 프로세스들이 굉장히 다양하게 존재하고

과제 진행에 따라 이런 저런 자체 요구사항이 추가 되었는데

그 중 까다로웠던 것 하나가

특정 작업에 속해있는 워커(자식 프로세스)에서 에러가 나는 경우

가장 최상단에 위치한 프로그램 진입점에 해당하는 프로세스(predict.py)를 멈추게 하는 것이었다.

 

채점 시스템은 predict.py를 실행 시키고

predict.py는 여러 process를 띄우며, process들은 subprocess들을 띄운다.

 

트래킹 시스템을 구성하는 워커들이 동작하다 에러가 나는경우,

채점 시스템이 에러로 인식하기 위해선 predict.py에게 에러를 전파해줘야한다.

 

하지만 우리가 개발한 시스템은 워커에 해당하는 모듈들이 predict.py에 임포트되어

하나의 프로그램으로 패키징 되어 있지 않고

독립적인 process 혹은 subprocess로 실행되는 구조다.

 

애초에 하나로 엮었다면 좋았겠지만, 사정이 있어 그렇게 개발되어왔고,

문제를 해결해야 하는 상황이었다.

 

시스템에 대해 간략히 설명하면

가장 먼저, 각 작업의 마스터들이 워커들에게 작업을 뿌리고

워커들은 결과를 또 다른 워커들에게 전해주거나

작업 결과를 머지해주는 또 다른 마스터에게 전해줌으로써

작업이 일련의 파이프라인을 거치면서 완료되는 구조다.

 

문제는 파이썬 subprocess 모듈을 통해서

별도의 스크립트로 개발 된 자식 프로세스(워커)들을 여러 개 띄웠기 때문에

자식에서 에러가 나더라도,

기본적으론 부모가 종료되지 않는다. (부모가 종료될 경우, 자식들은 모두 종료된다)

이렇게 되면, predict.py를 실행시킨 채점 시스템이

시스템에 에러가 발생했는지 여부를 인식할 수가 없다.

 

subprocess 모듈의 기능 중 자식 프로세스가 다 실행 될 때 까지 기다렸다가

결과 및 에러를 받아 볼 수 있는 기능이 있긴 하지만

부모와 자식이 서로 비동기로 동작해야만 하는 구조라서 띄우기만 하는 구조다.

 

이 때 간편하게 문제를 해결할 수 있는 방법 중 하나는

linux 의 signal 을 이용하는 것이다.

 

자식에서 문제가 발생할 경우

 subprocess.Popen(['pkill', '-15', '-ef', 'predict'])

위와 같이 -15 SIGTERM signal을 predict.py (가장 최상단의 시스템 진입점)으로 보내준다.

 

이후에 predict.py에선 다음과 같이 signal에 대한 이벤트 핸들러를 등록해 줄 수 있다.

핸들러 안에선 raise Exception을 해줌으로써 predict.py를 실행시킨 곳으로 에러 상황을 알려준다.

def sigterm_handler(_signo, _stack_frame):
    with open('/root/logs/error.log') as f:
        print(f.read())

    ft = time.time()
    print('time:\t{}'.format(ft-st))

    raise Exception('bye')

signal.signal(signal.SIGTERM, sigterm_handler)

 

이런식으로.. 독립적으로 실행된 프로세스간의 에러 전파가 가능했다.