Django源碼分析(一):自動重啓

初試 - 文件變化後 server 自動重啓

在此以前,不妨先了解下 django 是如何作到自動重啓的python

開始

django 使用 runserver 命令的時候,會啓動倆個進程。django

runserver 主要調用了 django/utils/autoreload.pymain 方法。
至於爲什麼到這裏的,咱們這裏不做詳細的贅述,後面篇章會進行說明。多線程

主線程經過 os.stat 方法獲取文件最後的修改時間進行比較,繼而從新啓動 django 服務(也就是子進程)。函數

大概每秒監控一次。spa

# django/utils/autoreload.py 的 reloader_thread 方法

def reloader_thread():
    ...
    # 監聽文件變化
    # -- Start
    # 這裏主要使用了 `pyinotify` 模塊,由於目前可能暫時導入不成功,使用 else 塊代碼
    # USE_INOTIFY 該值爲 False
    if USE_INOTIFY:
        fn = inotify_code_changed
    else:
        fn = code_changed
    # -- End
    while RUN_RELOADER:
        change = fn()
        if change == FILE_MODIFIED:
            sys.exit(3)  # force reload
        elif change == I18N_MODIFIED:
            reset_translations()
        time.sleep(1)
複製代碼

code_changed 根據每一個文件的最好修改時間是否發生變動,則返回 True 達到重啓的目的。線程

父子進程&多線程

關於重啓的代碼在 python_reloader 函數內rest

# django/utils/autoreload.py

def restart_with_reloader():
    import django.__main__
    while True:
        args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions]
        if sys.argv[0] == django.__main__.__file__:
            # The server was started with `python -m django runserver`.
            args += ['-m', 'django']
            args += sys.argv[1:]
        else:
            args += sys.argv
        new_environ = {**os.environ, 'RUN_MAIN': 'true'}
        exit_code = subprocess.call(args, env=new_environ)
        if exit_code != 3:
            return exit_code


def python_reloader(main_func, args, kwargs):
    # 一開始環境配置是沒有該變量的,全部走的是 else 語句塊
    if os.environ.get("RUN_MAIN") == "true":
        # 開啓一個新的線程啓動服務
        _thread.start_new_thread(main_func, args, kwargs)
        try:
            # 程序接着向下走,監控文件變化
            # 文件變化,退出該進程,退出碼反饋到了 subprocess.call 接收處...
            reloader_thread()
        except KeyboardInterrupt:
            pass
    else:
        try:
            # 而在 restart_with_reloader 這個函數設置了 RUN_MAIN 變量
            exit_code = restart_with_reloader()
            if exit_code < 0:
                os.kill(os.getpid(), -exit_code)
            else:
                sys.exit(exit_code)
        except KeyboardInterrupt:
            pass
複製代碼

程序啓動,由於沒有 RUN_MAIN 變量,因此走的 else 語句塊。code

頗爲有趣的是,restart_with_reloader 函數中使用 subprocess.call 方法執行了啓動程序的命令( e.g:python3 manage.py runserver ),此刻 RUN_MAIN 的值爲 True ,接着執行 _thread.start_new_thread(main_func, args, kwargs) 開啓新線程,意味着啓動了 django 服務。server

若是子進程不退出,則停留在 call 方法這裏(進行請求處理),若是子進程退出,退出碼不是3,while 則被終結。反之就繼續循環,從新建立子進程。進程

檢測文件修改

具體檢測文件發生改變的函數實現。

# django/utils/autoreload.py

def code_changed():
    global _mtimes, _win
    # 獲取全部文件
    for filename in gen_filenames():
        # 經過 os 模塊查看每一個文件的狀態
        stat = os.stat(filename)
        # 獲取最後修改時間
        mtime = stat.st_mtime
        if _win:
            mtime -= stat.st_ctime
        if filename not in _mtimes:
            _mtimes[filename] = mtime
            continue
        # 比較是否修改
        if mtime != _mtimes[filename]:
            _mtimes = {}
            try:
                del _error_files[_error_files.index(filename)]
            except ValueError:
                pass
            return I18N_MODIFIED if filename.endswith('.mo') else FILE_MODIFIED
    return False
複製代碼

總結

以上就是 django 檢測文件修改而達到重啓服務的實現流程。

結合 subprocess.call 和 環境變量 建立倆個進程。主進程負責監控子進程和重啓子進程。 子進程下經過開啓一個新線程(也就是 django 服務)。主線程監控文件變化,若是變化則經過 sys.exit(3) 來退出子進程,父進程獲取到退出碼不是3則繼續循環建立子進程,反之則退出整個程序。

好,到這裏。咱們勇敢的邁出了第一步,咱們繼續下一個環節!!! ヾ(◍°∇°◍)ノ゙

相關文章
相關標籤/搜索