Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Refactor all uses of thread locals to be more consistant and sane.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15232 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit fcbf881d8200f3771dd07be63e94171dd9548b92 1 parent 964cf1b
Alex Gaynor authored January 17, 2011
24  django/core/urlresolvers.py
@@ -8,6 +8,7 @@
8 8
 """
9 9
 
10 10
 import re
  11
+from threading import local
11 12
 
12 13
 from django.http import Http404
13 14
 from django.conf import settings
@@ -17,7 +18,6 @@
17 18
 from django.utils.functional import memoize
18 19
 from django.utils.importlib import import_module
19 20
 from django.utils.regex_helper import normalize
20  
-from django.utils.thread_support import currentThread
21 21
 
22 22
 _resolver_cache = {} # Maps URLconf modules to RegexURLResolver instances.
23 23
 _callable_cache = {} # Maps view and url pattern names to their view functions.
@@ -25,10 +25,11 @@
25 25
 # SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
26 26
 # the current thread (which is the only one we ever access), it is assumed to
27 27
 # be empty.
28  
-_prefixes = {}
  28
+_prefixes = local()
29 29
 
30 30
 # Overridden URLconfs for each thread are stored here.
31  
-_urlconfs = {}
  31
+_urlconfs = local()
  32
+
32 33
 
33 34
 class ResolverMatch(object):
34 35
     def __init__(self, func, args, kwargs, url_name=None, app_name=None, namespaces=None):
@@ -401,7 +402,7 @@ def set_script_prefix(prefix):
401 402
     """
402 403
     if not prefix.endswith('/'):
403 404
         prefix += '/'
404  
-    _prefixes[currentThread()] = prefix
  405
+    _prefixes.value = prefix
405 406
 
406 407
 def get_script_prefix():
407 408
     """
@@ -409,27 +410,22 @@ def get_script_prefix():
409 410
     wishes to construct their own URLs manually (although accessing the request
410 411
     instance is normally going to be a lot cleaner).
411 412
     """
412  
-    return _prefixes.get(currentThread(), u'/')
  413
+    return getattr(_prefixes, "value", u'/')
413 414
 
414 415
 def set_urlconf(urlconf_name):
415 416
     """
416 417
     Sets the URLconf for the current thread (overriding the default one in
417 418
     settings). Set to None to revert back to the default.
418 419
     """
419  
-    thread = currentThread()
420 420
     if urlconf_name:
421  
-        _urlconfs[thread] = urlconf_name
  421
+        _urlconfs.value = urlconf_name
422 422
     else:
423  
-        # faster than wrapping in a try/except
424  
-        if thread in _urlconfs:
425  
-            del _urlconfs[thread]
  423
+        if hasattr(_urlconfs, "value"):
  424
+            del _urlconfs.value
426 425
 
427 426
 def get_urlconf(default=None):
428 427
     """
429 428
     Returns the root URLconf to use for the current thread if it has been
430 429
     changed from the default one.
431 430
     """
432  
-    thread = currentThread()
433  
-    if thread in _urlconfs:
434  
-        return _urlconfs[thread]
435  
-    return default
  431
+    return getattr(_urlconfs, "value", default)
5  django/db/backends/__init__.py
@@ -25,6 +25,11 @@ def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS):
25 25
         self.alias = alias
26 26
         self.use_debug_cursor = None
27 27
 
  28
+        # Transaction related attributes
  29
+        self.transaction_state = []
  30
+        self.savepoint_state = 0
  31
+        self.dirty = None
  32
+
28 33
     def __eq__(self, other):
29 34
         return self.alias == other.alias
30 35
 
103  django/db/transaction.py
@@ -25,6 +25,7 @@
25 25
 from django.conf import settings
26 26
 from django.db import connections, DEFAULT_DB_ALIAS
27 27
 
  28
+
28 29
 class TransactionManagementError(Exception):
29 30
     """
30 31
     This exception is thrown when something bad happens with transaction
@@ -32,19 +33,6 @@ class TransactionManagementError(Exception):
32 33
     """
33 34
     pass
34 35
 
35  
-# The states are dictionaries of dictionaries of lists. The key to the outer
36  
-# dict is the current thread, and the key to the inner dictionary is the
37  
-# connection alias and the list is handled as a stack of values.
38  
-state = {}
39  
-savepoint_state = {}
40  
-
41  
-# The dirty flag is set by *_unless_managed functions to denote that the
42  
-# code under transaction management has changed things to require a
43  
-# database commit.
44  
-# This is a dictionary mapping thread to a dictionary mapping connection
45  
-# alias to a boolean.
46  
-dirty = {}
47  
-
48 36
 def enter_transaction_management(managed=True, using=None):
49 37
     """
50 38
     Enters transaction management for a running thread. It must be balanced with
@@ -58,15 +46,14 @@ def enter_transaction_management(managed=True, using=None):
58 46
     if using is None:
59 47
         using = DEFAULT_DB_ALIAS
60 48
     connection = connections[using]
61  
-    thread_ident = thread.get_ident()
62  
-    if thread_ident in state and state[thread_ident].get(using):
63  
-        state[thread_ident][using].append(state[thread_ident][using][-1])
  49
+
  50
+    if connection.transaction_state:
  51
+        connection.transaction_state.append(connection.transaction_state[-1])
64 52
     else:
65  
-        state.setdefault(thread_ident, {})
66  
-        state[thread_ident][using] = [settings.TRANSACTIONS_MANAGED]
67  
-    if thread_ident not in dirty or using not in dirty[thread_ident]:
68  
-        dirty.setdefault(thread_ident, {})
69  
-        dirty[thread_ident][using] = False
  53
+        connection.transaction_state.append(settings.TRANSACTIONS_MANAGED)
  54
+
  55
+    if connection.dirty is None:
  56
+        connection.dirty = False
70 57
     connection._enter_transaction_management(managed)
71 58
 
72 59
 def leave_transaction_management(using=None):
@@ -78,16 +65,18 @@ def leave_transaction_management(using=None):
78 65
     if using is None:
79 66
         using = DEFAULT_DB_ALIAS
80 67
     connection = connections[using]
  68
+
81 69
     connection._leave_transaction_management(is_managed(using=using))
82  
-    thread_ident = thread.get_ident()
83  
-    if thread_ident in state and state[thread_ident].get(using):
84  
-        del state[thread_ident][using][-1]
  70
+    if connection.transaction_state:
  71
+        del connection.transaction_state[-1]
85 72
     else:
86  
-        raise TransactionManagementError("This code isn't under transaction management")
87  
-    if dirty.get(thread_ident, {}).get(using, False):
  73
+        raise TransactionManagementError("This code isn't under transaction "
  74
+            "management")
  75
+    if connection.dirty:
88 76
         rollback(using=using)
89  
-        raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
90  
-    dirty[thread_ident][using] = False
  77
+        raise TransactionManagementError("Transaction managed block ended with "
  78
+            "pending COMMIT/ROLLBACK")
  79
+    connection.dirty = False
91 80
 
92 81
 def is_dirty(using=None):
93 82
     """
@@ -96,7 +85,9 @@ def is_dirty(using=None):
96 85
     """
97 86
     if using is None:
98 87
         using = DEFAULT_DB_ALIAS
99  
-    return dirty.get(thread.get_ident(), {}).get(using, False)
  88
+    connection = connections[using]
  89
+
  90
+    return connection.dirty
100 91
 
101 92
 def set_dirty(using=None):
102 93
     """
@@ -106,11 +97,13 @@ def set_dirty(using=None):
106 97
     """
107 98
     if using is None:
108 99
         using = DEFAULT_DB_ALIAS
109  
-    thread_ident = thread.get_ident()
110  
-    if thread_ident in dirty and using in dirty[thread_ident]:
111  
-        dirty[thread_ident][using] = True
  100
+    connection = connections[using]
  101
+
  102
+    if connection.dirty is not None:
  103
+        connection.dirty = True
112 104
     else:
113  
-        raise TransactionManagementError("This code isn't under transaction management")
  105
+        raise TransactionManagementError("This code isn't under transaction "
  106
+            "management")
114 107
 
115 108
 def set_clean(using=None):
116 109
     """
@@ -120,9 +113,10 @@ def set_clean(using=None):
120 113
     """
121 114
     if using is None:
122 115
         using = DEFAULT_DB_ALIAS
123  
-    thread_ident = thread.get_ident()
124  
-    if thread_ident in dirty and using in dirty[thread_ident]:
125  
-        dirty[thread_ident][using] = False
  116
+    connection = connections[using]
  117
+
  118
+    if connection.dirty is not None:
  119
+        connection.dirty = False
126 120
     else:
127 121
         raise TransactionManagementError("This code isn't under transaction management")
128 122
     clean_savepoints(using=using)
@@ -130,9 +124,8 @@ def set_clean(using=None):
130 124
 def clean_savepoints(using=None):
131 125
     if using is None:
132 126
         using = DEFAULT_DB_ALIAS
133  
-    thread_ident = thread.get_ident()
134  
-    if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
135  
-        del savepoint_state[thread_ident][using]
  127
+    connection = connections[using]
  128
+    connection.savepoint_state = 0
136 129
 
137 130
 def is_managed(using=None):
138 131
     """
@@ -140,10 +133,9 @@ def is_managed(using=None):
140 133
     """
141 134
     if using is None:
142 135
         using = DEFAULT_DB_ALIAS
143  
-    thread_ident = thread.get_ident()
144  
-    if thread_ident in state and using in state[thread_ident]:
145  
-        if state[thread_ident][using]:
146  
-            return state[thread_ident][using][-1]
  136
+    connection = connections[using]
  137
+    if connection.transaction_state:
  138
+        return connection.transaction_state[-1]
147 139
     return settings.TRANSACTIONS_MANAGED
148 140
 
149 141
 def managed(flag=True, using=None):
@@ -156,15 +148,16 @@ def managed(flag=True, using=None):
156 148
     if using is None:
157 149
         using = DEFAULT_DB_ALIAS
158 150
     connection = connections[using]
159  
-    thread_ident = thread.get_ident()
160  
-    top = state.get(thread_ident, {}).get(using, None)
  151
+
  152
+    top = connection.transaction_state
161 153
     if top:
162 154
         top[-1] = flag
163 155
         if not flag and is_dirty(using=using):
164 156
             connection._commit()
165 157
             set_clean(using=using)
166 158
     else:
167  
-        raise TransactionManagementError("This code isn't under transaction management")
  159
+        raise TransactionManagementError("This code isn't under transaction "
  160
+            "management")
168 161
 
169 162
 def commit_unless_managed(using=None):
170 163
     """
@@ -221,13 +214,11 @@ def savepoint(using=None):
221 214
         using = DEFAULT_DB_ALIAS
222 215
     connection = connections[using]
223 216
     thread_ident = thread.get_ident()
224  
-    if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
225  
-        savepoint_state[thread_ident][using].append(None)
226  
-    else:
227  
-        savepoint_state.setdefault(thread_ident, {})
228  
-        savepoint_state[thread_ident][using] = [None]
  217
+
  218
+    connection.savepoint_state += 1
  219
+
229 220
     tid = str(thread_ident).replace('-', '')
230  
-    sid = "s%s_x%d" % (tid, len(savepoint_state[thread_ident][using]))
  221
+    sid = "s%s_x%d" % (tid, connection.savepoint_state)
231 222
     connection._savepoint(sid)
232 223
     return sid
233 224
 
@@ -239,8 +230,8 @@ def savepoint_rollback(sid, using=None):
239 230
     if using is None:
240 231
         using = DEFAULT_DB_ALIAS
241 232
     connection = connections[using]
242  
-    thread_ident = thread.get_ident()
243  
-    if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
  233
+
  234
+    if connection.savepoint_state:
244 235
         connection._savepoint_rollback(sid)
245 236
 
246 237
 def savepoint_commit(sid, using=None):
@@ -251,8 +242,8 @@ def savepoint_commit(sid, using=None):
251 242
     if using is None:
252 243
         using = DEFAULT_DB_ALIAS
253 244
     connection = connections[using]
254  
-    thread_ident = thread.get_ident()
255  
-    if thread_ident in savepoint_state and using in savepoint_state[thread_ident]:
  245
+
  246
+    if connection.savepoint_state:
256 247
         connection._savepoint_commit(sid)
257 248
 
258 249
 ##############
12  django/utils/thread_support.py
... ...
@@ -1,12 +0,0 @@
1  
-"""
2  
-Code used in a couple of places to work with the current thread's environment.
3  
-Current users include i18n and request prefix handling.
4  
-"""
5  
-
6  
-try:
7  
-    import threading
8  
-    currentThread = threading.currentThread
9  
-except ImportError:
10  
-    def currentThread():
11  
-        return "no threading"
12  
-
31  django/utils/translation/trans_real.py
@@ -7,15 +7,16 @@
7 7
 import warnings
8 8
 import gettext as gettext_module
9 9
 from cStringIO import StringIO
  10
+from threading import local
10 11
 
11 12
 from django.utils.importlib import import_module
12 13
 from django.utils.safestring import mark_safe, SafeData
13  
-from django.utils.thread_support import currentThread
  14
+
14 15
 
15 16
 # Translations are cached in a dictionary for every language+app tuple.
16 17
 # The active translations are stored by threadid to make them thread local.
17 18
 _translations = {}
18  
-_active = {}
  19
+_active = local()
19 20
 
20 21
 # The default translation is based on the settings file.
21 22
 _default = None
@@ -197,16 +198,15 @@ def activate(language):
197 198
             "Please use the 'nb' translation instead.",
198 199
             DeprecationWarning
199 200
         )
200  
-    _active[currentThread()] = translation(language)
  201
+    _active.value = translation(language)
201 202
 
202 203
 def deactivate():
203 204
     """
204 205
     Deinstalls the currently active translation object so that further _ calls
205 206
     will resolve against the default translation object, again.
206 207
     """
207  
-    global _active
208  
-    if currentThread() in _active:
209  
-        del _active[currentThread()]
  208
+    if hasattr(_active, "value"):
  209
+        del _active.value
210 210
 
211 211
 def deactivate_all():
212 212
     """
@@ -214,11 +214,11 @@ def deactivate_all():
214 214
     useful when we want delayed translations to appear as the original string
215 215
     for some reason.
216 216
     """
217  
-    _active[currentThread()] = gettext_module.NullTranslations()
  217
+    _active.value = gettext_module.NullTranslations()
218 218
 
219 219
 def get_language():
220 220
     """Returns the currently selected language."""
221  
-    t = _active.get(currentThread(), None)
  221
+    t = getattr(_active, "value", None)
222 222
     if t is not None:
223 223
         try:
224 224
             return t.to_language()
@@ -246,8 +246,9 @@ def catalog():
246 246
     This can be used if you need to modify the catalog or want to access the
247 247
     whole message catalog instead of just translating one string.
248 248
     """
249  
-    global _default, _active
250  
-    t = _active.get(currentThread(), None)
  249
+    global _default
  250
+
  251
+    t = getattr(_active, "value", None)
251 252
     if t is not None:
252 253
         return t
253 254
     if _default is None:
@@ -262,9 +263,10 @@ def do_translate(message, translation_function):
262 263
     translation object to use. If no current translation is activated, the
263 264
     message will be run through the default translation object.
264 265
     """
  266
+    global _default
  267
+
265 268
     eol_message = message.replace('\r\n', '\n').replace('\r', '\n')
266  
-    global _default, _active
267  
-    t = _active.get(currentThread(), None)
  269
+    t = getattr(_active, "value", None)
268 270
     if t is not None:
269 271
         result = getattr(t, translation_function)(eol_message)
270 272
     else:
@@ -300,9 +302,9 @@ def gettext_noop(message):
300 302
     return message
301 303
 
302 304
 def do_ntranslate(singular, plural, number, translation_function):
303  
-    global _default, _active
  305
+    global _default
304 306
 
305  
-    t = _active.get(currentThread(), None)
  307
+    t = getattr(_active, "value", None)
306 308
     if t is not None:
307 309
         return getattr(t, translation_function)(singular, plural, number)
308 310
     if _default is None:
@@ -587,4 +589,3 @@ def get_partial_date_formats():
587 589
     if month_day_format == 'MONTH_DAY_FORMAT':
588 590
         month_day_format = settings.MONTH_DAY_FORMAT
589 591
     return year_month_format, month_day_format
590  
-
8  tests/regressiontests/i18n/tests.py
@@ -4,10 +4,12 @@
4 4
 import os
5 5
 import sys
6 6
 import pickle
  7
+from threading import local
7 8
 
8 9
 from django.conf import settings
9 10
 from django.template import Template, Context
10  
-from django.utils.formats import get_format, date_format, time_format, localize, localize_input, iter_format_modules
  11
+from django.utils.formats import (get_format, date_format, time_format,
  12
+    localize, localize_input, iter_format_modules)
11 13
 from django.utils.importlib import import_module
12 14
 from django.utils.numberformat import format as nformat
13 15
 from django.utils.safestring import mark_safe, SafeString, SafeUnicode
@@ -61,7 +63,7 @@ def test_pgettext(self):
61 63
         self.old_locale_paths = settings.LOCALE_PATHS
62 64
         settings.LOCALE_PATHS += (os.path.join(os.path.dirname(os.path.abspath(__file__)), 'other', 'locale'),)
63 65
         from django.utils.translation import trans_real
64  
-        trans_real._active = {}
  66
+        trans_real._active = local()
65 67
         trans_real._translations = {}
66 68
         activate('de')
67 69
 
@@ -649,7 +651,7 @@ def setUp(self):
649 651
         from django.utils.translation import trans_real
650 652
         # Okay, this is brutal, but we have no other choice to fully reset
651 653
         # the translation framework
652  
-        trans_real._active = {}
  654
+        trans_real._active = local()
653 655
         trans_real._translations = {}
654 656
         activate('de')
655 657
 

0 notes on commit fcbf881

Please sign in to comment.
Something went wrong with that request. Please try again.