I was maintaining a huge Django app. Everything looked fine until I tried to run python manage.py -h:
1
2
3
4
5
6
7
8
9
10
11
12
Traceback (most recent call last):
File "manage.py", line 15, in <module>
execute_from_command_line(sys.argv) File "venv/lib/python3.6/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
utility.execute() File "venv/lib/python3.6/site-packages/django/core/management/__init__.py", line 357, in execute
django.setup() File "venv/lib/python3.6/site-packages/django/__init__.py", line 24, in setup
apps.populate(settings.INSTALLED_APPS) File "venv/lib/python3.6/site-packages/django/apps/registry.py", line 83, in populate
raise RuntimeError("populate() isn't reentrant")RuntimeError: populate() isn't reentrant
Hmmm, okay.
The exception was from execute_from_command_line in manage.py, which is generated by Django thus in great chance not the root cause of the exception.
I googled populate() isn't reentrant but theresults weren't helpful enough to me.
Let's dig into Django's source code then. Followed the traceback from exception we can locate the populate function
1
2
3
4
5
6
7
# An RLock prevents other threads from entering this section. The# compare and set operation below is atomic.ifself.loading:# Prevent reentrant calls to avoid running AppConfig.ready()# methods twice.raiseRuntimeError("populate() isn't reentrant")self.loading=True
Looks like the populate got called twice. Since execute_from_command_line is expected, let's record the traceback from the other call and print it out.
# An RLock prevents other threads from entering this section. The# compare and set operation below is atomic.ifself.loading:# Prevent reentrant calls to avoid running AppConfig.ready()# methods twice.raiseRuntimeError("populate() isn't reentrant.\n"+"\n".join(self.prev_traceback))self.loading=Trueimporttracebackself.prev_traceback=traceback.format_stack()
Rerun python manage.py -h, and we can see the nice traceback.
Looks like someone called django.setup() in our celery module.
1
2
3
4
5
# set the default Django settings module for the 'celery' program.os.environ.setdefault('DJANGO_SETTINGS_MODULE','balisong_server.settings')django.setup()app=Celery('balisong')
After talking with the code author and looking into celery's document, I'm definite that the django.setup() here should be removed.
And removing this solves the problem.
Lessons learned: when in doubt, just print traceback.