Анализ исходного кода Django (1): автоматический перезапуск

Django

Эта статья основана наdjango-2.1.xСерия версий написана.

Начальный тест - после изменения файлаserverавтоматический перезапуск

Перед этим давайте разбиратьсяdjangoКак сделать автоматический перезапуск

Начинать

djangoиспользоватьrunserverПри выполнении команды запускаются два процесса.

runserverВ основном называетсяdjango/utils/autoreload.pyВнизmainметод.
Что касается того, почему мы пришли сюда, мы не будем здесь вдаваться в подробности и объясним это в следующих главах.

основная нить черезos.statМетод получает время последней модификации файла для сравнения, а затем перезапускаетсяdjangoСлужбы (они же дочерние процессы).

Мониторинг примерно раз в секунду.

# 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внутри функции


# 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.

Достаточно интересно,restart_with_reloaderиспользуется в функцииsubprocess.callметод выполняет команду для запуска программы (например, python3 manage.py runserver), в данный моментRUN_MAINценностьTrue, затем выполните_thread.start_new_thread(main_func, args, kwargs)Открытие новой темы означает началоdjangoСлужить.

Если дочерний процесс не завершается, он остается вcallМетод здесь (для обработки запроса), если дочерний процесс завершается, код выхода не 3, то время завершается. В противном случае цикл продолжается, и дочерний процесс создается заново.

Обнаружение модификаций файлов

Реализация функции, специально обнаруживающей изменения в файле.


# 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, в противном случае он завершит всю программу.

Хорошо, поехали. Мы смело сделали первый шаг, продолжаем по следующей ссылке! ! !ヾ(◍°∇°◍)ノ゙