2021년 10월 4일 월요일

python flask 의 자동 리로드 기능 구현(How to make flask's automatic reload function)


이번에는 자동 리로드(자신의 소스를 수정하면 자동으로 재시작하는)기능을 직접 만들어 보겠습니다. 시작하기전에 몇가지 알아둬야 하는 상식이 있는데 이건 따로 아래 링크를 참고하세요

0. python flask 의 자동 리로드 기능 구현 원리(How to implement flask's automatic reload function) : https://swlock.blogspot.com/2021/10/python-flask-how-to-implement-flasks.html

1. subprocess call,run,Popen 차이 및 간단 사용법 : https://swlock.blogspot.com/2021/10/python-subprocess-callpopenrun.html

2. 파일이 변화 하였는지 감시하기 : https://swlock.blogspot.com/2021/10/python-monitor-if-file-has-changed.html


이제 본격적인 시작입니다.

구현을 위해서는 먼저 자신이 자신을 실행시키는 구조에 대해 이해해야합니다.

그래서 이해하기 쉽게 도식도를 그려봤습니다.



위 그림은 cmd line shell에서 python 스크립트 실행하는것이 (1) 이 됩니다. 해당 스크립트가 실행중이다가 재시작을 해야 한다고 판단될때 (2)를 실행하게 됩니다. 그러면서 기존에 있는 프로세서는 wait 상태가 됨을 의미합니다.

이걸 구현하는 방법은 subprocess의 blocking 방식의 run/call을 사용하면 됩니다.

즉 subprocess.run() -> subprocess.run() -> subprocess.run() 이런 형태가 됩니다. 여기에서 큰 문제가 하나가 있는데 중간에 process가 종료되지 않고 쌓이게 됨으로서 memory/process leak이 발생하게 됩니다.

네모 상자는 Process를 의미합니다. 화살표는 Process를 실행하는 subprocss를 의미하거나 종료됨을 의미합니다.

그럼 leak이 발생하지 않도록 이번에는 Popen을 이용해보도록 합시다.


형태는 위와 같이 됩니다. (2)가 호출되면서 process는 종료되면서 (3),(5)처럼 됩니다. leak이 발생하는 부분은 해결되었습니다. 그러나 여기에 문제가 있는데 shell에서 볼때 shell이 실행시킨 프로세서가 종료됨으로서 cmd line으로 돌아온 상태로 process와 통신을 할 수 없는 상태가 되어버립니다. 이를 해결하는 방법은 두가지 방법을 적당히 섞는 것입니다.


딱 봐도 구성이 복잡합니다. (2)(5)번이 reload로 자기자신을 실행시키는 구간이며 처음에는 (2) 가 호출되며 (2)에 의해서 process가 호출할때 blocking 방식으로 호출합니다. 그리고 reload구간에 진입하면 reload를 하는게 아니라 (3)에 의해서 process를 종료시킵니다. 이때 이전 process가 blocking에 의해서 집입되어 있으므로 blocking이 해제됩니다. 이 부분을 무한 루프로 구현하여 (4)가 되면서 새로운 process (5)를 생성시킵니다. 그리고 reload될때 (6)에 의해서 종료되는 방식입니다.

코드를 보면 좀 더 자세히 이해가 될겁니다.

import time
import os
import subprocess
import sys

import _19_check_file_mod

def get_args_for_reloading():
	py_exe = [sys.executable]
	py_script = sys.argv[0]
	args = sys.argv[1:]
	print(py_exe,py_script,args)
	
	ret=[]
	ret.extend(py_exe)
	ret.append(py_script)
	ret.extend(args)
	return ret

def reload():
	print("reload")
	args = get_args_for_reloading()
	new_environ = os.environ.copy()
	if os.environ.get("RE_LOAD")=="true":
		sys.exit(0)
		print("never")
	else:
		while True:
			sys.stdout.flush()
			new_environ["RE_LOAD"] = "true"
			exit_code = subprocess.call(args, env=new_environ)
			print("exit_code:",exit_code)
			time.sleep(1)
			
	print("never")

if __name__ == '__main__':
	check_file_mod = _19_check_file_mod.check_file_mod(sys.argv[0])
	print("start !")
	if os.environ.get("RE_LOAD")=="true":
		print("reloaded")
		
	while True:
		if check_file_mod.is_change():
			print("change")
			reload()
		time.sleep(2)

파일 변화 검사는 

check_file_mod = _19_check_file_mod.check_file_mod(sys.argv[0])

위 코드이며 다음 링크에서 참고 하기 바랍니다. 2. 파일이 변화 하였는지 감시하기 : https://swlock.blogspot.com/2021/10/python-monitor-if-file-has-changed.html


shell에 의해서 처음 실행된 프로세서 그림에서 (1)은 os.environ.get("RE_LOAD") 값이 없게 됩니다. 그래서 while 구간으로 진입해서 아래와 같은 코드에 머무르게 됩니다.

		while True:
			sys.stdout.flush()
			new_environ["RE_LOAD"] = "true"
			exit_code = subprocess.call(args, env=new_environ)
			print("exit_code:",exit_code)
			time.sleep(1)

이 부분이 (4)(7) 번 위치의 코드입니다.

	if os.environ.get("RE_LOAD")=="true":
		sys.exit(0)
		print("never")

그리고, 위 구간은 (3)(6) 번 처럼 돌아오기 위한 구간이 되겠습니다.


테스트 방법

테스트는 위 예제를 실행 후 실행한 파일을 수정하면 됩니다.

실행시 import 에러가 발생한다면 2. 링크의 게시물을 보시고 파일을 하나 더 추가해주면 됩니다.



댓글 없음:

댓글 쓰기