Home > Back-end >  Why django runserver command starts 2 processes? What are they for? And how to distinguish between e
Why django runserver command starts 2 processes? What are they for? And how to distinguish between e

Time:08-22

While building some standalone Django app, which will be running tasks in the background as a separate daemon Thread, I ran into a problem because it seemed as if there are two MainThreads when starting the Django server, each Thread has a different id. After digging more into the problem, it turned out that it's because it's actually two processes.

Experiement:

  1. Run django-admin startproject example && cd example to start a new project.
  2. Edit example/settings.py adding imoprt os if it's not already imported and then add the line print(f"Current processes ID:{os.getpid()}")
  3. Run python manage.py runserver and look at the output
Current processes ID:426286
Current processes ID:426288
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

August 21, 2022 - 15:30:42
Django version 2.2.12, using settings 'example.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
  1. Change any file (for example just add a new line to settings.py) and save and look at the output
/pathtoproject/example/example/settings.py changed, reloading.
Current processes ID:426417
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

August 21, 2022 - 15:32:07
Django version 2.2.12, using settings 'example.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C. 

Observation

manage.py runserver starts 2 processes. One of them remains until the server is stopped, while the other one is terminated and recreated with another id every time the project is reloaded due to any file changes.

Version details

This has been tested on multiple versions of Django 2, 3, and 4 and it always starts 2 processes as explained above.

CodePudding user response:

One of the 2 processes is for auto-reloader.
Use --noreload with runserver.

CodePudding user response:

There isn't much official documentation about the StateReloader but thankfully I was able to figure it out from the code. So when starting Django, it basically uses the built-in python library subprocess to create a new python process with exactly the same arguments as in this line in the source code. The value of the variable args would be something like

['/usr/bin/python', 'manage.py', 'runserver']

A very important question to ask now is, if running python manage.py runserver starts a new instance of python manage.py runserver then what's preventing it from going through a closed loop where it creates a new process again and again until the OS crashes after running out of RAM?

The answer to this question is also the answer to the main question and it's in this line in the source code

        if os.environ.get(DJANGO_AUTORELOAD_ENV) == "true":

Django uses an environment variable defined as DJANGO_AUTORELOAD_ENV to figure out whether the current process is the Django process initiated by the Statereloader or the process of the Statereloader itself.

The value of DJANGO_AUTORELOAD_ENV as defined in this line is "RUN_MAIN" and has been consistently the same since Django 2 until today in Django 4.

So if you add the following example code in settings.py:

if os.environ.get('RUN_MAIN') == 'true':
    print("This process has been initiated by Statereloader")

The print statement will be executed only once when you start the app, however, it will be executed again every time the app reloads. If you want it to be executed only once you start the app and never again regardless of Statereloader then it should be like this:

if os.environ.get('RUN_MAIN') != 'true':
    print("This is the original process and will never restart")

Also, a useful tip, if you need to run a background task along with Django without it interfering with how Django works, you can start a daemon thread inside the AppConfig.ready method AFTER checking the RUN_MAIN environment variable as below.

from django.apps import AppConfig
import threading
import os


class RockNRollConfig(AppConfig):
    # ...

    def ready(self):
        if os.environ.get('RUN_MAIN') == 'true':
             threading.Thread(
                  name="YourDaemonThread",
                  target=your_thread_function,
                  daemon=True
             ).start()

This way you can rest assured that:

  1. Only one instance of your thread will be running at a time (Unless you start multiple instances of the Django server)
  2. Your thread will be restarted along with everything else in the Django app whenever the Statereloader recreates the process.
  • Related