How Django Works (2) Built-in Server, Autoreload

Django's built-in server

Django comes with a built-in server for development and testing purpose.

The command to start the built-in server is
python manage.py runserver 
 

Autoreload

One notable feature of the built-in server is its ability to automatically reload once a file is changed. Accord to Official document, "The development server automatically reloads Python code for each request, as needed. You don’t need to restart the server for code changes to take effect. However, some actions like adding files don’t trigger a restart, so you’ll have to restart the server in these cases." The feature can be shut off by --noreload.

This blog will examine in implementation detail how autoreload is implemented and how autoreload is used by Django built-in server.

The file that implements autoreload is site-packages/django/utils/autoreload.py
if django is installed. Or you can find it at https://github.com/django/django/blob/master/django/utils/autoreload.py

Django's built-in server uses autoreload

The autoreload is used in site-packages/django/core/management/commands/runserver.py

class Command(BaseCommand):
... 

    def run(self, **options):
        """
        Runs the server, using the autoreloader if needed
        """
        use_reloader = options.get('use_reloader')

        if use_reloader:
            autoreload.main(self.inner_run, None, options)
        else:
            self.inner_run(None, **options)

    def inner_run(self, *args, **options):
        threading = options.get('use_threading')
        shutdown_message = options.get('shutdown_message', '')
        quit_command = 'CTRL-BREAK' if sys.platform == 'win32' else 'CONTROL-C'

        self.stdout.write("Performing system checks...\n\n")
        self.check(display_num_errors=True)
        self.check_migrations()
        now = datetime.now().strftime('%B %d, %Y - %X')
        if six.PY2:
            now = now.decode(get_system_encoding())
        self.stdout.write(now)
        self.stdout.write((
            "Django version %(version)s, using settings %(settings)r\n"
            "Starting development server at http://%(addr)s:%(port)s/\n"
            "Quit the server with %(quit_command)s.\n"

  1. By default, use_reloader is True, run will call inner_run indirectly through autoreload module
  2. If --noreload is used use_reloader is set to False, run will call inner_run directly.

How autoreload works

  1. The main process spawn a new process and wait for the result from spawn. While the child process is executed, main process is blocked. 
  2. Child process starts a new thread that carry out real work (in the case of Django inner_run). 
  3. Child process's main thread loops and checks weather any file from sys.modules has been changed, if so main thread exit with exit value set to 3.
  4. Upon return from spawn, main process checks if exit value is 3. If so, main process spawn a child process again. Otherwise, the main process exit.
It works because main thread is different from other thread in that if main thread exits, the process terminates and other threads belonging to the process will be killed.

I've written a small case that debugs the whole process using pudb. The autoreload file was also modified to remove references to Django.
https://github.com/druckenclam/code4blogs/tree/master/Django_Autoreload 

Popular posts from this blog

LeetCode 68 Text Justification (C++, Python)

How Django Works (4) URL Resolution

Python Class Method and Class Only Method