1 | from __future__ import with_statement
|
---|
2 | from django.core.handlers.wsgi import WSGIHandler as DjangoWSGIHandler
|
---|
3 |
|
---|
4 | from threading import Lock
|
---|
5 |
|
---|
6 | class WSGIHandler(DjangoWSGIHandler):
|
---|
7 | """
|
---|
8 | This provides a threadsafe drop-in replacement of django's WSGIHandler.
|
---|
9 |
|
---|
10 | Initialisation of django via a multithreaded wsgi handler is not safe.
|
---|
11 | It is vulnerable to a A-B B-A deadlock.
|
---|
12 |
|
---|
13 | When two threads bootstrap django via different urls you have a change to hit
|
---|
14 | the following deadlock.
|
---|
15 |
|
---|
16 | thread 1 thread 2
|
---|
17 | view A view B
|
---|
18 | import file foo import lock foo import file bar import lock bar
|
---|
19 | bootstrap django lock AppCache.write_lock
|
---|
20 | import file bar import lock bar <-- blocks
|
---|
21 | bootstrap django lock AppCache.write_lock <----- deadlock
|
---|
22 |
|
---|
23 | workaround for an AB BA deadlock: wrap it in a lock C.
|
---|
24 |
|
---|
25 | lock C lock C
|
---|
26 | lock A lock B
|
---|
27 | lock B lock A
|
---|
28 | release B release A
|
---|
29 | release A release A
|
---|
30 | release C release C
|
---|
31 |
|
---|
32 |
|
---|
33 | Thats exactly what this class does, but... only for the first few calls.
|
---|
34 | After that we remove the lock C. as the AppCache.write_lock is only held when django is booted.
|
---|
35 |
|
---|
36 | If we would not remove the lock C after the first few calls, that would make the whole app single threaded again.
|
---|
37 |
|
---|
38 | Usage:
|
---|
39 | in your wsgi file replace the following lines
|
---|
40 | import django.core.handlers.wsgi.WSGIHandler
|
---|
41 | application = django.core.handlers.wsgi.WSGIHandler
|
---|
42 | by
|
---|
43 | import threadsafe_wsgi
|
---|
44 | application = threadsafe_wsgi.WSGIHandler
|
---|
45 |
|
---|
46 |
|
---|
47 | FAQ:
|
---|
48 | Q: why would you want threading in the first place ?
|
---|
49 | A: to reduce memory. Big apps can consume hundeds of megabytes each. adding processes is then much more expensive than threads.
|
---|
50 | that memory is better spend caching, when threads are almost free.
|
---|
51 |
|
---|
52 | Q: this deadlock, it looks far-fetched, is this real ?
|
---|
53 | A: yes we had this problem on production machines.
|
---|
54 | """
|
---|
55 | __initLock = Lock() # lock C
|
---|
56 | __initialized = 0
|
---|
57 |
|
---|
58 | def __call__(self, environ, start_response):
|
---|
59 | # the first calls (4) we squeeze everybody through lock C
|
---|
60 | # this basically serializes all threads
|
---|
61 | MIN_INIT_CALLS = 4
|
---|
62 | if self.__initialized < MIN_INIT_CALLS:
|
---|
63 | with self.__initLock:
|
---|
64 | ret = DjangoWSGIHandler.__call__(self, environ, start_response)
|
---|
65 | self.__initialized += 1
|
---|
66 | return ret
|
---|
67 | else:
|
---|
68 | # we are safely bootrapped, skip lock C
|
---|
69 | # now we are running multi-threaded again
|
---|
70 | return DjangoWSGIHandler.__call__(self, environ, start_response)
|
---|