werkzeug reloader 机制
2.2.1 介绍
我们在本地开发flask应用时,常常会用到debug模式,类似这样app.run(debug=True)他为我们调试代码带来了很多方便,其中就包括代码修改后本地服务的自动reload。但这个功能并不是flask提供的,而是werkzeug。整个过程大概的流程是这样的
将inner()函数传入到run_with_reloader()
reloader = reloader_loops[reloader_type](extra_files, interval)
signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
try:
if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
ensure_echo_on()
t = threading.Thread(target=main_func, args=())
t.setDaemon(True)
t.start()
reloader.run()
else:
sys.exit(reloader.restart_with_reloader())
except KeyboardInterrupt:
pass
这一段代码的是根据环境变量WERKZEUG_RUN_MAIN来判断当前进程是不是子进程,如果是子进程,那么环境变量的值就为true,后面我们会看到在主进程去生成进程的时候会设置这个变量。如果是子进程,其实就是开了一个线程用于跑我们的inner(),然后在子进程的主线程去启动我们在上方选择的reloader。
werkzeug有两种reloader,StatReloaderLoop和WatchdogReloaderLoop,默认是StatReloaderLoop通过遍历整个项目下的文件mtime和上次reload的存的进行比较。而WatchdogReloaderLoop则是借助第三方包
def restart_with_reloader(self):
"""Spawn a new Python interpreter with the same arguments as the
current one, but running the reloader thread.
"""
while 1:
_log("info", f" * Restarting with {self.name}")
args = _get_args_for_reloading()
new_environ = os.environ.copy()
new_environ["WERKZEUG_RUN_MAIN"] = "true"
exit_code = subprocess.call(args, env=new_environ, close_fds=False)
if exit_code != 3:
return exit_code
到此就是整个reloader的实现的核心部分,
-
_get_args_for_reloading()这个是获取的是运行当前进程的执行参数,也就是我们启动flask的完整命令["python", "xxx.py"]类似这样。
-
new_environ = os.environ.copy()是复制当前的环境变量,并添加环境变量WERKZEUG_RUN_MAIN。
-
标准库的subprocess.call()这是阻塞式的系统命令执行方式,并指定运行环境变量,需要注意的是,这个subprocess.call()此时是阻塞的,且如果当前主进程异常退出,是会为我们kill掉生成的子进程。
-
exit_code != 3:这是父进程用来判断子进程是否需要重启的重要判断,因为如果是文件改动,子进程的reloader将会调用sys.exit(3)退出子进程。
其实到此我们也清楚了整个werkzeug reloader的具体实现方式:父进程轮询创建子进程的步骤,并监控子进程的退出码来判断是否要退出轮询,而子进程就是父进程的所有运行环境的copy,只是通过环境变量WERKZEUG_RUN_MAIN来控制代码要走的逻辑分支。