werkzeug中reloader的实现

在用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: 167-130-643

可以看出服务器会自动检测是哪个文件发生了改变并自动重启,接下来就看看它是怎么实现的,这是当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()来调用函数,程序中也没地方捕获这个异常啊以及那个信号处理函数的作用,两者有关系吗有大牛帮忙解答一下吗

时间: 2024-10-11 22:24:59

werkzeug中reloader的实现的相关文章

关于flask线程安全的简单研究

flask是python web开发比较主流的框架之一,也是我在工作中使用的主要开发框架.一直对其是如何保证线程安全的问题比较好奇,所以简单的探究了一番,由于只是简单查看了源码,并未深入细致研究,因此以下内容仅为个人理解,不保证正确性. 首先是很多文章都说flask会为每一个request启动一个线程,每个request都在单独线程中处理,因此保证了线程安全.于是就做了一个简单的测试.首先是写一个简单的flask程序(只需要有最简单的功能用于测试即可),然后我们知道一个flask应用启动之后实际

flask源代码笔记——路由

那flask是如何将路由与视图函数绑定的呢?在Flask类的内部是这样定义的: def route(self, rule, **options): def decorator(f): self.add_url_rule(rule, f.__name__, **options) self.view_functions[f.__name__] = f return f return decorator self.view_functions = {} def add_url_rule(self, r

Flask 扩展 缓存

如果同一个请求会被多次调用,每次调用都会消耗很多资源,并且每次返回的内容都相同,就该使用缓存了 自定义缓存装饰器 在使用Flask-Cache扩展实现缓存功能之前,我们先来自己写个视图缓存装饰器,方便我们来理解视图缓存的实现.首先,我们要有一个缓存,Werkzeug框架中的提供了一个简单的缓存对象SimpleCache,它是将缓存项存放在Python解释器的内存中,我们可以用下面的代码获取SimpleCache的缓存对象: from werkzeug.contrib.cache import S

Flask 应用最佳实践

一个好的应用目录结构可以方便代码的管理和维护,一个好的应用管理维护方式也可以强化程序的可扩展性 应用目录结构 假定我们的应用主目录是"flask-demo",首先我们建议每个应用都放在一个独立的包下,假设包名是"myapp".所以,整个应用的目录结构如下: flask-demo/ ├ run.py # 应用启动程序 ├ config.py # 环境配置 ├ requirements.txt # 列出应用程序依赖的所有Python包 ├ tests/ # 测试代码包

【编程书籍 大系】 计算机开放电子书汇总

计算机开放电子书汇总 站点 站点源码 100个gcc小技巧 在线阅读 PDF格式 EPUB格式 MOBI格式 100个gdb小技巧 在线阅读 PDF格式 EPUB格式 MOBI格式 关于浏览器和网络的 20 项须知 在线阅读 PDF格式 EPUB格式 MOBI格式 2015互联网企业校招笔试题 MEGA下载 Github下载 3周3页面 在线阅读 PDF格式 EPUB格式 MOBI格式 简明 Python 教程 在线阅读 PDF格式 EPUB格式 A Guide to HTML5 and CSS

Flask的上下文管理机制

前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__getitem__系列. __call__ 这个方法相信大家并不陌生,在单例模式中,我们可能用到过,除此之外,还想就没有在什么特殊场景中用到了.我们往往忽视了它一个很特殊的用法:对象object+()或者类Foo()+()这种很特殊的用法.在Flask上下文管理中,入口就是使用了这种方式. __getit

flask 文件上传(单文件上传、多文件上传)

文件上传 在HTML中,渲染一个文件上传字段只需要将<input>标签的type属性设为file,即<input type=”file”>. 这会在浏览器中渲染成一个文件上传字段,单击文件选择按钮会打开文件选择窗口,选择对应的文件后,被选择的文件名会显示在文件选择按钮旁边. 在服务器端,可以和普通数据一样获取上传文件数据并保存.不过需要考虑安全问题,文件上传的漏洞也是比较流行的攻击方式.除了常规的CSRF防范,我们还需要重点关注这几个问题:验证文件类型.验证文件大小.过滤文件名 定

Flask源码复习之路由

构建路由规则 一个 web 应用不同的路径会有不同的处理函数,路由就是根据请求的 URL 找到对应处理函数的过程. 在执行查找之前,需要有一个规则列表,它存储了 url 和处理函数的对应关系.最容易想到的解决方案就是定义一个字典,key 是 url,value 是对应的处理函数.如果 url 都是静态的(url 路径都是实现确定的,没有变量和正则匹配),那么路由的过程就是从字典中通过 url 这个 key ,找到并返回对应的 value:如果没有找到,就报 404 错误.而对于动态路由,还需要更

flask项目深度研究之localstack

重启博客,记录一些零散的知识和复习学习过的知识 今天记录的是关于localstack的一些知识,首先需要讲到python原生的threadlocal a = 10 import threading def test(): print(a) thread = threading.Thread(target=test) thread.start() thread.join() 如上,有一个变量a,若线程相对其进行操作,可以将其作为参数传进去,如果要对其进行修改,则需要加锁,操作起来很麻烦,为了解决这