在用flask开发时,如果把use_reloader设为True(debug设为True也能实现),那当你修改了app代码或调用环境发生改变时,服务器会自动重启,如下

 * Detected change in '/home/steinliber/flask-source-code/route/a.py', reloading
* Restarting with stat
* Debugger is active!
* Debugger pin code: --

可以看出服务器会自动检测是哪个文件发生了改变并自动重启,接下来就看看它是怎么实现的,这是当use_reloader开启时相关函数调用情况

/usr/lib/python2.7/threading.py(783)__bootstrap()
-> self.__bootstrap_inner()
/usr/lib/python2.7/threading.py(810)__bootstrap_inner()
-> self.run()
/usr/lib/python2.7/threading.py(763)run()
-> self.__target(*self.__args, **self.__kwargs)
/home/steinliber/flask-source-code/env/local/lib/python2.7/site-packages/werkzeug/serving.py(657)inner()

可以看出该进程创建了线程并实现服务器功能

在函数run_simple中,会对use_reloader进行判断

  if use_reloader:
# If we're not running already in the subprocess that is the
# reloader we want to open up a socket early to make sure the
# port is actually available.
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
if port == 0 and not can_open_by_fd:
raise ValueError('Cannot bind to a random port with enabled '
'reloader if the Python interpreter does '
'not support socket opening by fd.') # Create and destroy a socket so that any exceptions are
# raised before we spawn a separate Python interpreter and
# lose this ability.
address_family = select_ip_version(hostname, port)
s = socket.socket(address_family, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((hostname, port))
if hasattr(s, 'set_inheritable'):
s.set_inheritable(True) # If we can open the socket by file descriptor, then we can just
# reuse this one and our socket will survive the restarts.
if can_open_by_fd:
os.environ['WERKZEUG_SERVER_FD'] = str(s.fileno())
s.listen(LISTEN_QUEUE)
log_startup(s)
else:
s.close() from ._reloader import run_with_reloader
run_with_reloader(inner, extra_files, reloader_interval,
reloader_type)

WERKZEUG_RUN_MAIN用于判断是否开启了子进程,如果没有进入子进程,就创建socket,(注释说的是创建并关闭socket去产生所有异常在创建子进程之前)若socket可以用fd打开就保持socket打开方便重用。接着就是调用run_with_reloader了,这是相关测试代码

def run_with_reloader(main_func, extra_files=None, interval=1,
reloader_type='auto'):
"""Run the given function in an independent python interpreter."""
import signal
reloader = reloader_loops[reloader_type](extra_files, interval)
signal.signal(signal.SIGTERM, doit)
try:
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
t = threading.Thread(target=main_func, args=())
t.setDaemon(True)
t.start()
print reloader,t
reloader.run()
else:
print 'here'
sys.exit(reloader.restart_with_reloader())
except KeyboardInterrupt:
pass

def doit(*args):
    print 'here im'
    sys.exit(0)


class ReloaderLoop(object):
name = None # monkeypatched by testsuite. wrapping with `staticmethod` is required in
# case time.sleep has been replaced by a non-c function (e.g. by
# `eventlet.monkey_patch`) before we get here
_sleep = staticmethod(time.sleep) def __init__(self, extra_files=None, interval=1):
self.extra_files = set(os.path.abspath(x)
for x in extra_files or ())
self.interval = interval def run(self):
pass def restart_with_reloader(self):
"""Spawn a new Python interpreter with the same arguments as this one,
but running the reloader thread.
"""
while 1:
_log('info', ' * Restarting with %s' % self.name)
args = [sys.executable] + sys.argv
new_environ = os.environ.copy()
new_environ['WERKZEUG_RUN_MAIN'] = 'true' # a weird bug on windows. sometimes unicode strings end up in the
# environment and subprocess.call does not like this, encode them
# to latin1 and continue.
print new_environ
print args
if os.name == 'nt' and PY2:
for key, value in iteritems(new_environ):
if isinstance(value, text_type):
new_environ[key] = value.encode('iso-8859-1') exit_code = subprocess.call(args, env=new_environ,
close_fds=False)
if exit_code != 3:
return exit_code def trigger_reload(self, filename):
self.log_reload(filename)
sys.exit(3) def log_reload(self, filename):
filename = os.path.abspath(filename)
_log('info', ' * Detected change in %r, reloading' % filename)

里面我加入了一些测试的print函数,当重启服务器时可以看到

* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
here
* Restarting with stat
{'LC_NUMERIC': 'zh_CN.UTF-8', 'WERKZEUG_SERVER_FD': '3', 'XDG_GREETER_DATA_DIR': '/var/lib/lightdm-data/steinliber', 'GNOME_DESKTOP_SESSION_ID': 'this-is-deprecated', 'LC_MEASUREMENT': 'zh_CN.UTF-8', 'UPSTART_EVENTS': 'started starting', 'XDG_CURRENT_DESKTOP': 'Unity', 'LC_PAPER': 'zh_CN.UTF-8', 'LOGNAME': 'steinliber', 'XDG_SEAT': 'seat0', 'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games', 'XDG_VTNR': '7', 'GNOME_KEYRING_CONTROL': '/run/user/1000/keyring-ZGJRph', 'ZSH': '/home/steinliber/.oh-my-zsh', 'DISPLAY': ':0', 'LANG': 'en_US.UTF-8', 'TERM': 'xterm', 'SHELL': '/usr/bin/zsh', 'XDG_SESSION_PATH': '/org/freedesktop/DisplayManager/Session0', 'XAUTHORITY': '/home/steinliber/.Xauthority', 'LANGUAGE': 'en_US', 'SHLVL': '1', 'MANDATORY_PATH': '/usr/share/gconf/ubuntu.mandatory.path', 'COMPIZ_CONFIG_PROFILE': 'ubuntu', 'UPSTART_INSTANCE': '', 'JOB': 'gnome-session', 'WINDOWID': '62921612', 'SESSIONTYPE': 'gnome-session', 'XMODIFIERS': '@im=fcitx', 'GPG_AGENT_INFO': '/run/user/1000/keyring-ZGJRph/gpg:0:1', 'HOME': '/home/steinliber', 'QT4_IM_MODULE': 'fcitx', 'SELINUX_INIT': 'YES', 'QT_QPA_PLATFORMTHEME': 'appmenu-qt5', 'XDG_RUNTIME_DIR': '/run/user/1000', 'GTK_IM_MODULE': 'fcitx', 'LC_ADDRESS': 'zh_CN.UTF-8', 'WERKZEUG_RUN_MAIN': 'true', 'SSH_AUTH_SOCK': '/run/user/1000/keyring-ZGJRph/ssh', 'VTE_VERSION': '3409', 'LC_CTYPE': 'en_US.UTF-8', 'GDMSESSION': 'ubuntu', 'UPSTART_JOB': 'unity-settings-daemon', 'UPSTART_SESSION': 'unix:abstract=/com/ubuntu/upstart-session/1000/1673', 'XDG_DATA_DIRS': '/usr/share/ubuntu:/usr/share/gnome:/usr/local/share/:/usr/share/', 'XDG_SEAT_PATH': '/org/freedesktop/DisplayManager/Seat0', 'XDG_SESSION_ID': 'c1', 'DBUS_SESSION_BUS_ADDRESS': 'unix:abstract=/tmp/dbus-8A5Emo2M1r', '_': '/usr/bin/python', 'DEFAULTS_PATH': '/usr/share/gconf/ubuntu.default.path', 'LC_IDENTIFICATION': 'zh_CN.UTF-8', 'DESKTOP_SESSION': 'ubuntu', 'LSCOLORS': 'Gxfxcxdxbxegedabagacad', 'XDG_CONFIG_DIRS': '/etc/xdg/xdg-ubuntu:/usr/share/upstart/xdg:/etc/xdg', 'GNOME_KEYRING_PID': '1763', 'OLDPWD': '/home/steinliber', 'GDM_LANG': 'en_US', 'LC_TELEPHONE': 'zh_CN.UTF-8', 'GTK_MODULES': 'overlay-scrollbar:unity-gtk-module', 'LC_MONETARY': 'zh_CN.UTF-8', 'INSTANCE': 'Unity', 'PWD': '/home/steinliber/werkzeug', 'COLORTERM': 'gnome-terminal', 'LC_NAME': 'zh_CN.UTF-8', 'LC_TIME': 'zh_CN.UTF-8', 'LESS': '-R', 'PAGER': 'less', 'USER': 'steinliber'}
['/usr/bin/python', 'a.py']
* Debugger is active!
* Debugger pin code: 291-357-613
<werkzeug._reloader.StatReloaderLoop object at 0x7f34c55c7950> <Thread(Thread-1, started daemon 139864609179392)>

 

也就是说在调用run_with_reloader时因为此时并不是子进程,会调用sys.exit(reloader.restart_with_reloader())这个方法,之后就调用reloader.restart_with_reloader(),想了好久不知道为什么以及如何通过sys.exit()来调用这个方法,信号处理函数也没触发,哪位大牛可以帮忙解答下?

之后在restart_with_reloader中,得到了当时的环境变量以及启动的程序名,把环境变量设为子进程运行,通过subprocess方法来创建子进程即重新运行app,在子进程中,在run_with_reloader中会创建一个线程来运行服务器函数inner(),并将该线程设为守护线程,当主线程终止时,这个进程会被强制结束,而这个子线程就负责创建服务器服务。父线程调用reloader.run(),

class StatReloaderLoop(ReloaderLoop):
name = 'stat' def run(self):
mtimes = {}
while 1:
for filename in chain(_iter_module_files(),
self.extra_files):
try:
mtime = os.stat(filename).st_mtime
except OSError:
continue old_time = mtimes.get(filename)
if old_time is None:
mtimes[filename] = mtime
continue
elif mtime > old_time:
self.trigger_reload(filename)
self._sleep(self.interval)

这是一个比较简单的reloader类,基本上的作用就是每隔参数提供的秒数就检测app所用的模块或文件的最后修改时间是否发生了改变,如果改变,就说明文件发生了修改,就调用trigger.reloader(filename)方法。这个方法就是登记了发现的改变,然后调用sys.exit(3),在run_with_reloader中可以看到若返回的状态码是3,就重新循环。重新取得模块环境以及APP名,然后再根据这些创建子进程。

这里要实现APP代码更改后,服务器马上根据新的配置重启,要先创建个子进程该进程取得当时的环境变量来调用,子进程又创建了子线程来运行服务器,而主线程就负责监视相关文件的变化,若发生了改变,就退出进程,子进程是守护进程也马上结束。主进程接受到状态码若为3,就重新进入循环,讲的有点绕,我的理解基本就是这样。

搞不明白的就是为什么要用sys.exit()来调用函数,程序中也没地方捕获这个异常啊以及那个信号处理函数的作用,两者有关系吗有大牛帮忙解答一下吗

werkzeug中reloader的实现的更多相关文章

  1. werkzeug中服务器处理请求的实现

    当成功建立好服务器后,接下来就是等待请求并处理请求通过路由分配给相应的视图函数了,以下是函数调用过程 -> self._handle_request_noblock() /usr/lib/pyt ...

  2. Werkzeug教程

    http://chaoxz2005.blog.163.com/blog/static/15036542012863405266/ http://www.dajo.com.cn/a/boke/pytho ...

  3. Werkzeug源码阅读笔记(四)

    今天主要讲一下werkzeug中的routing模块.这个模块是werkzeug中的重点模块,Flask中的路由相关的操作使用的都是这个模块 routing模块的用法 在讲解模块的源码之前,先讲讲这个 ...

  4. Werkzeug源码阅读笔记(三)

    这次主要讲下werkzeug中的Local. 源码在werkzeug/local.py Thread Local 在Python中,状态是保存在对象中.Thread Local是一种特殊的对象,它是对 ...

  5. 将 flask 中的 session 存储到 SQLite 数据库中

    将 flask 中的 session 存储到 SQLite 数据库中 使用 flask 构建服务器后端时,常需要在浏览器端存储 cookie 用于识别不同用户,根据不同的 cookie 判断出当前请求 ...

  6. flask中的上下文_请求上下文和应用上下文

    前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__get ...

  7. 用werkzeug实现一个简单的python web框架

    使用工具 Pycharm , Navicat , WebStorm等 使用库 Werkzeug用于实现框架的底层支撑,pymysql用于实现ORM,jinja2用于模板支持,json用于返回json数 ...

  8. 动手写一个简单的Web框架(Werkzeug路由问题)

    动手写一个简单的Web框架(Werkzeug路由问题) 继承上一篇博客,实现了HelloWorld,但是这并不是一个Web框架,只是自己手写的一个程序,别人是无法通过自己定义路由和返回文本,来使用的, ...

  9. Flask中请求数据的优雅传递

    当一个请求到来时,浏览器会携带很多信息发送发送服务端.在Django中,每一个处理函数都要传入一个request的参数,该参数携带所有请求的信息,也就是服务端程序封装的environ(不明白该参数可以 ...

随机推荐

  1. Element DOM Tree jQuery plugin – Firebug like functionality | RockingCode

    Element DOM Tree jQuery plugin – Firebug like functionality | RockingCode Element DOM Tree jQuery pl ...

  2. Checking Network Configuration requirements Failed

    安装oracle执行检查,出现 Checking Network Configuration requirements ... Check complete. The overall result o ...

  3. Tomcat从零开始(十)Loader

    第十课: 不知不觉就10篇blog了,说实话,我是第一次更这么长时间的Blog. 嗯,今天说说Loader,在以前的课程中,也就是内个能使用最初级的servlet的那一节,我们使用了URLClassL ...

  4. 【Tomcat】使用Eclipse运行Tomcat7源码

    1.搭建开发环境 a.下载好tomcat7源码包 b.安装好jdk7,及设置好环境变量 c.安装好ant,及设置好环境变量,用于预编译tocmat源码. d.以上步骤准备好我们就可以开始进入源码的预编 ...

  5. 我们在 web 应用开发过程中经常遇到输出某种编码的字 符, 如 iso8859-1 等, 如何输出一个某种编码的字符串?

    public String translate (String str) { String tempStr = ""; try { tempStr = new String(str ...

  6. python的reduce()函数

    reduce()函数也是Python内置的一个高阶函数. reduce()函数接收的参数和 map()类似,一个函数 f,一个list,但行为和 map()不同,reduce()传入的函数 f 必须接 ...

  7. PowerShell Remove all user defined variable in PowerShell

    When PS scripts executes, it is possibly create much user defined variables. So, sometimes these var ...

  8. IOS 学习笔记(5) 控件 文本视图(UITextView)的使用方法

    相对于UILabell所支持的较短文本内容,UITextView对于长文本的支持更好.UITextView能够以滚动的方式全部浏览到长文本,并且就像UILabel那样,从ISO6,他也提供了对NSAt ...

  9. excel读入数据库

    POI3.9效率大幅度提高,支持xls以及xlsx. 首先需要POI的JAR包,MAVEN配置如下: <!-- excel start --> <dependency> < ...

  10. FPGA知识大梳理(四)FPGA中的复位系统大汇总

    本文整合特权(吴厚航)和coyoo(王敏志)两位大神的博文.我也很推崇这两位大神的书籍,特权的书籍要偏基础一下,大家不要一听我这么说就想买coyoo的.我还是那一句话,做技术就要step by ste ...