79

I want to execute some code at startup of Django server but I want it to run only once. Currently when I start the server it's executed twice. Documentation says that this might happen and:

you should put a flag on your AppConfig classes to prevent re-running code which should be executed exactly one time.

Any idea how to achieve this? Print statement below is still executed twice.

from django.apps import AppConfig
import app.mqtt
from apscheduler.schedulers.background import BackgroundScheduler

class MyAppConfig(AppConfig):
    name = 'app'
    verbose_name = "HomeIoT"
    run_already = False

    def ready(self):
        if MyAppConfig.run_already: return
        MyAppConfig.run_already = True
        print("Hello")
Moe Far
  • 2,742
  • 2
  • 23
  • 41
Koooop
  • 791
  • 1
  • 5
  • 4
  • Is that indentation correct? `ready` should be inside the MyAppConfig class. – Daniel Roseman Nov 19 '15 at 21:28
  • 1
    You are right. It was just copy/paste mistake. I fixed it. – Koooop Nov 20 '15 at 08:27
  • 4
    Does the double-printing happen if you run with `--noreload`? – AKX Nov 20 '15 at 08:38
  • 2
    Nice. Thanks! Starting the server with `--noreload` actually solved this. However is it possible avoid multiple runs of ready() even without this? My understanding is that ready() will be still executed when running any manage.py command. – Koooop Nov 20 '15 at 20:19
  • 2
    And if you don't use `runserver` - like in production... - it does not work. What does --noreload do to achieve this? – JulienD Apr 25 '16 at 12:52
  • See the bottom of [this answer](http://stackoverflow.com/a/16111968/1599229). – scharfmn Jul 05 '16 at 13:36
  • @bahmait I want more natural way...! I don't want this to run using --noreload. Still I can't figure where and what kind of flag I put this to make work. Is it django doc is wrong ? https://docs.djangoproject.com/en/1.9/ref/applications/#django.apps.AppConfig.ready – Raja Simon Jul 08 '16 at 12:32
  • https://stackoverflow.com/questions/37441564/redefinition-of-appconfig-ready maybe resolve your question! – RedBencity Jan 04 '18 at 08:50
  • [enter link description here](https://stackoverflow.com/questions/37441564/redefinition-of-appconfig-ready) Maybe resolve your question. – RedBencity Jan 04 '18 at 08:55
  • Thanks, and import any model in the ready block so that the model is already loaded – msanjay May 26 '22 at 05:46

6 Answers6

59

When you use python manage.py runserver, Django starts two processes, one for the actual development server, the other to reload your application when the code changes.

You can start the server without the reload option, and you will see only one process running:

python manage.py runserver --noreload

See also the ready() method running twice in Django.

dfrankow
  • 20,191
  • 41
  • 152
  • 214
RedBencity
  • 709
  • 5
  • 5
41

if you don't want to use --noreload you can:

replace the line in your app's __init__.py that you use to specify the config:

default_app_config = 'mydjangoapp.apps.MydjangoappConfig'

by this:

import os

if os.environ.get('RUN_MAIN', None) != 'true':
    default_app_config = 'mydjangoapp.apps.MydjangoappConfig'

Or, check for the RUN_MAIN environment variable within your AppConfig ready method:

import os

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'app'

    def ready(self):
        if os.environ.get('RUN_MAIN'):
            print('Hello')
phoenix
  • 7,988
  • 6
  • 39
  • 45
Mohamed Benkedadra
  • 1,964
  • 3
  • 21
  • 48
  • 18
    You can also just check the `RUN_MAIN` env var in the `ready()` method itself. – alexdlaird Jan 23 '19 at 00:39
  • 5
    Checking the `RUN_MAIN` environment variable in `ready()` is _so_ much simpler than dealing with file locks or thread locks. Thank you! – culix Sep 21 '19 at 17:55
  • 5
    I did like @alexdlaird said: check RUN_MAIN in the ready method, and it works like a charm. I think this answer should be the good answer, because --noreload works for runserver, but not for instance when you do `python manage.py collectstatic` : without the RUN_MAIN check guard, your code will be exectude, and you probably don't want. – Nico Jun 11 '21 at 15:27
7

I found this worked for me without using the --noreload flag in python manage.py runserver.

Check for an environment variable in the ready() method. The env variable does not persist after the application ends but DOES persist if the code changes are detected by the server and after it automatically reloads itself.

# File located in mysite/apps.py

from django.apps import AppConfig
import os

class CommandLineRunner(AppConfig):
    name = 'mysite'

    def ready(self):
        run_once = os.environ.get('CMDLINERUNNER_RUN_ONCE') 
        if run_once is not None:
            return
        os.environ['CMDLINERUNNER_RUN_ONCE'] = 'True' 

        # The code you want to run ONCE here
  
Daniel H.
  • 73
  • 2
  • 6
  • 2
    Is there any risk of a race condition, if the function gets called twice from different threads? And is there a chance that variable might be defined in the environment, so that the code might never get run at all? You can probably contract the environment to avoid that, but I'd still be worried about the race condition. – joanis Jun 14 '21 at 19:25
  • 1
    @joanis Great point! I have modified the code so that immediately after the env var check, it sets it (instead of setting it after the commands ran). Hopefully this will reduce or eliminate any potential race conditions! – Daniel H. Jun 15 '21 at 16:16
3

You need to implement locking. It is not a simple problem and the solution will not feel natural as you are dealing with processes and threads. Be warned there are many answers to the problem of locking, some simpler approaches:

A file lock: Ensure a single instance of an application in Linux (note that threads share file lock by default so this answer needs to be expanded to account for threads).

There is also this answer which uses a Python package called tendo that encapsulates the a file lock implementation: https://stackoverflow.com/a/1265445/181907

Django itself provides an abstracted portable file locking utility in django.core.files.locks.

Roberto Rosario
  • 1,818
  • 1
  • 17
  • 31
2

As Roberto mentioned you will need to implement locking in order to do this when running your server through the runserver command, if you want to use the default auto_reload functionality.

Django implements it's auto_reload via threading and so imports the AppConfig in two separate threads, the main 'command/watch' thread and the 'reload' thread running the server. Add a print statement to the module and you will see this in action. The 'main' thread loads the AppConfig files as part of it's BaseCommand execute, and the 'reload' thread then load them again during it's startup of the server.

If you have code that can not be run in both of these threads then your options are somewhat limited. You can implement a thread lock so that the 'reload' thread will not run ready(); you can move to a production environment to run your server (Gunicorn for example is very quick to setup, even for testing); or you can call your method in another way rather than using ready().

I would advise moving to a proper environment, but the best option really depends on what exactly the method you are calling is supposed to do.

Airs
  • 2,014
  • 1
  • 12
  • 10
  • You are confusing threads with processes here. As mentioned in the top answer, Django start two processes, not threads for auto reload. – John29 Jun 09 '20 at 17:27
  • Thanks for the correction. I will point out though that you are incorrect about Django not starting two threads. At least in 2016 Djano also spawned a reload_thread in the child server process which handled killing the process when changes were detected. The main process simply respawned the child when it died with exit code 3, signifying it should be reloaded. Hence my confusion at the time. – Airs Jul 01 '20 at 20:57
2

It was found that AppConfig was fired twice and caused the scheduler to be started twice with this configuration. Instead, instantiate the scheduler in urls.py as shown below:

urlpatterns = [
    path('api/v1/', include(router.urls)),
    path('api/v1/login/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/v1/login/refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'),
    path('api/v1/', include('rest_registration.api.urls'))
]

scheduler = BackgroundScheduler()
scheduler.add_job(task.run, trigger='cron', hour=settings.TASK_RUNNER_HOURS, minute=settings.TASK_RUNNER_MINUTES, max_instances=1)
scheduler.start()
Marcos Paolo
  • 301
  • 2
  • 7
  • This is actually very clever. Not the most elegant solution, but just works since the urls would be loaded only one time. – camposer Aug 29 '23 at 11:31