2021년 6월 5일 토요일

Python timeout을 가지는 subprocess


외부 프로세서를 실행시키는 명령은 subprocess 모듈이 있습니다.

하지만 간혹 subprocess로 실행 시켰던 모듈이 응답 없는 상태에 빠지면 어떻게 될까요?

이러한 사항을 방지하고자 communicate() 메소드에 timeout 인자가 있습니다.


https://docs.python.org/ko/3/library/subprocess.html

timeout 인자는 Popen.communicate()로 전달됩니다. 시간제한이 만료되면, 자식 프로세스를 죽이고 기다립니다. 자식 프로세스가 종료된 후 TimeoutExpired 예외가 다시 발생합니다.


해당 기능을 사용하더라도 자식 프로세서가 정상적으로 종료되지 않는 경우가 발생합니다.

그래서 timeout에 걸리더라도 생성한 자식들을 모두 종료하는 예제를 만들었습니다.


import subprocess
import psutil
import os
import threading

class TimeoutThread(threading.Thread):
	def __init__(self, pid, timeout, event):
		threading.Thread.__init__(self)
		self.pid = pid
		self.timeout = timeout
		self.event = event
		self.setDaemon(True)
		self.istimeout = False

	def kill(self,proc_pid):
		process = psutil.Process(proc_pid)
		for proc in process.children(recursive=True):
			proc.kill()
		process.kill()
	
	def run(self):
		self.event.wait(self.timeout)
		if not self.event.isSet():
			self.istimeout = True
			try:
				self.kill(self.pid)
			except Exception as e:
				print(e)
				pass

def subprocess_timed(path_cmd, timeout, pipe=True):
	event = threading.Event()
	abspath = os.path.abspath(path_cmd)
	head, tail = os.path.split(abspath)
	if pipe==True:
		proc = subprocess.Popen(tail, cwd = head, shell=False, stdout=subprocess.PIPE)
	else:
		proc = subprocess.Popen(tail, cwd = head, shell=False)
	tothread = TimeoutThread(proc.pid, timeout, event)
	tothread.start()
	
	# waiting for finishing subprocess
	(outs, errs) = proc.communicate()
	event.set()
	
	return (tothread.istimeout,proc.returncode, outs, errs)
	
if __name__ == "__main__":
	print("** Test1 **")
	print(subprocess_timed("python 8_exit_code.py",6))
	print("** Test2 **")
	print(subprocess_timed("python 8_exit_code_with_sleep.py",6))
	print("** Test3 **")
	print(subprocess_timed("python 8_exit_code_with_sleep.py",4))
	print("** Test4 **")
	print(subprocess_timed("python 8_exit_code.py",6,pipe=False))


8_exit_code.py

print("Hello")
exit(99)


8_exit_code_with_sleep.py

import time
print ("Sleep 5 seconds from now on...")
time.sleep(5)
exit(88)


실행결과

** Test1 **
(False, 99, b'Hello\r\n', None)
** Test2 **
(False, 88, b'Sleep 5 seconds from now on...\r\n', None)
** Test3 **
(True, 15, b'', None)
** Test4 **
Hello
(False, 99, None, None)



만든 함수는 subprocess_timed 입니다. 

리턴값으로 4개를 받는데 첫번째는 timeout이 발생 정보입니다. True이면 timeout이 발생해서 종료했다는 값입니다. 두번째는 return value 입니다. 세번째는 표준 출력과 표준 에러값을 받습니다. 이 표준값을 정상적으로 받기위해서는 pipe 인자가 True가 되어야합니다.

Test4 예제를 보시면 pipe가 False가 되는경우 pipe를 통해서 값을 전달 받지 않기 때문에 subprocess의 출력이 콘솔에 직접 나타나게 됩니다.



댓글 없음:

댓글 쓰기