#26152 closed Cleanup/optimization (fixed)
django.setup() hangs django if ever used in a module that's imported by an app
Reported by: | Harry Percival | Owned by: | nobody |
---|---|---|---|
Component: | Documentation | Version: | 1.9 |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Accepted | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
The docs suggest using django.setup() for any script that needs to be run standalone:
But this causes problems if a standalone script also contains some code that is imported by a regular django app.
Here's a minimal repro:
if foo.py contains django.setup(), and myapp.models.py tries to import from foo.py, that now causes a hang such that no manage.py call will ever complete. ouch!
The solution/workaround is to put any calls to django.setup() inside a
if __name__ == '__main__': import django django.setup()
I gather that fixing django.setup() / apps.populate() to make it reentrant is hard. So this could be a quick documentation fix instead?
cf #18251, a more complex version of this bug I think, which is in fact quite simple to run into...
Change History (16)
comment:1 by , 9 years ago
comment:3 by , 9 years ago
Component: | Uncategorized → Documentation |
---|---|
Type: | Uncategorized → Cleanup/optimization |
How about in the topics/settings section that you linked to in the description? Something like, "When writing standlone scripts, keep them as minimal as possible and put application logic in other modules of your application. You can't import scripts that call django.setup()
in your application as this will lead to a deadlock."
comment:6 by , 9 years ago
Triage Stage: | Unreviewed → Accepted |
---|
I don't think it's a good practice to write "standalone scripts" that are imported Django itself.
comment:7 by , 9 years ago
We may be able to detect that case and raise an exception instead of hanging. (I didn't look at the code too closely.)
comment:8 by , 9 years ago
Hi Aymeric, there's a couple of ways I can think of that would detect cycles and then a couple of things we could do with it:
detect options:
- import the traceback module and examine the call stack for a previous call to django.setup()
- set some global variable in the module, _setup_called = True, or whatever it may be. (there is already a class variable called .ready, which is set to true at the end of the apps.populate() call. So maybe we could just add a second class variable that's set at the beginning)
what to do:
- raise an exception
- or, just an early return. Maybe setup() should just be idempotent
comment:9 by , 9 years ago
Just been digging into this. And I am not an experienced coder of multithreaded code, so I probably shouldn't be allowed.
Here's the current code:
# populate() might be called by two threads in parallel on servers # that create threads before initializing the WSGI callable. with self._lock: if self.ready: return # app_config should be pristine, otherwise the code below won't # guarantee that the order matches the order in INSTALLED_APPS. if self.app_configs: raise RuntimeError("populate() isn't reentrant") # [...] rest of populate code self.ready = True
So switching _lock to being a threading.RLock will have the desired effect of raising the "populate isn't reentrant" exception that's already been defined, if populate is ever called recursively by the same thread.
I'm not sure I understand the code though. Under what circumstances, with the current threading.Lock(), would we expect the RuntimeError to be encountered?
comment:10 by , 9 years ago
Yes, I was thinking of adding a boolean flag when the process starts.
I /think/ we discussed using a RLock before and rejected it, but I'm not sure. I have to investigate. I'll try to find time.
comment:11 by , 9 years ago
I might be wrong but, given the use of threading.Lock(), isn't the RunTimeError if self.app_configs theoretically impossible?
comment:13 by , 9 years ago
Hi all, can I propose a re-open? We just got bitten by this thing today again, where calling django.setup() was messing with the logging setup in weird and unpredictable ways in our unit tests.
It seems like the ideal version of django.setup() should be idempotent, or only able to be called once?
Or should I open a different ticket?
comment:15 by , 9 years ago
I guess a new ticket is appropriate since the "Documentation" bit has been completed.
comment:16 by , 8 years ago
Harry: eventually I found the time to investigate this issue and filed #27176. Feel free to comment on that issue if you have anything to add.
I want to submit a documentation fix, but I'm not sure where. The new docs don't really have a section that specifically recommends using django.setup() for standalone scripts?