Fixed #26888 -- Fixed concurrency issue in URL resolver.
Fixed a regression in 625b8e9295d79650208bfb3fca8bf9e6aaf578e4: improper short-circuiting could lead to a KeyError when threads concurrently call RegexURLResolver._populate().
This commit is contained in:
parent
3e71f6544f
commit
389a5318a0
|
@ -9,6 +9,7 @@ from __future__ import unicode_literals
|
|||
|
||||
import functools
|
||||
import re
|
||||
import threading
|
||||
from importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -164,7 +165,7 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||
# urlpatterns
|
||||
self._callback_strs = set()
|
||||
self._populated = False
|
||||
self._populating = False
|
||||
self._local = threading.local()
|
||||
|
||||
def __repr__(self):
|
||||
if isinstance(self.urlconf_name, list) and len(self.urlconf_name):
|
||||
|
@ -178,9 +179,13 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||
)
|
||||
|
||||
def _populate(self):
|
||||
if self._populating:
|
||||
# Short-circuit if called recursively in this thread to prevent
|
||||
# infinite recursion. Concurrent threads may call this at the same
|
||||
# time and will need to continue, so set 'populating' on a
|
||||
# thread-local variable.
|
||||
if getattr(self._local, 'populating', False):
|
||||
return
|
||||
self._populating = True
|
||||
self._local.populating = True
|
||||
lookups = MultiValueDict()
|
||||
namespaces = {}
|
||||
apps = {}
|
||||
|
@ -213,7 +218,7 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
|
||||
for app_name, namespace_list in pattern.app_dict.items():
|
||||
apps.setdefault(app_name, []).extend(namespace_list)
|
||||
if not pattern._populating:
|
||||
if not getattr(pattern._local, 'populating', False):
|
||||
pattern._populate()
|
||||
self._callback_strs.update(pattern._callback_strs)
|
||||
else:
|
||||
|
@ -225,7 +230,7 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||
self._namespace_dict[language_code] = namespaces
|
||||
self._app_dict[language_code] = apps
|
||||
self._populated = True
|
||||
self._populating = False
|
||||
self._local.populating = False
|
||||
|
||||
@property
|
||||
def reverse_dict(self):
|
||||
|
|
|
@ -5,6 +5,7 @@ Unit tests for reverse URL lookups.
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from admin_scripts.tests import AdminScriptTestCase
|
||||
|
||||
|
@ -429,6 +430,18 @@ class ResolverTests(SimpleTestCase):
|
|||
self.assertTrue(resolver._is_callback('urlpatterns_reverse.nested_urls.View3'))
|
||||
self.assertFalse(resolver._is_callback('urlpatterns_reverse.nested_urls.blub'))
|
||||
|
||||
def test_populate_concurrency(self):
|
||||
"""
|
||||
RegexURLResolver._populate() can be called concurrently, but not more
|
||||
than once per thread (#26888).
|
||||
"""
|
||||
resolver = RegexURLResolver(r'^/', 'urlpatterns_reverse.urls')
|
||||
resolver._local.populating = True
|
||||
thread = threading.Thread(target=resolver._populate)
|
||||
thread.start()
|
||||
thread.join()
|
||||
self.assertNotEqual(resolver._reverse_dict, {})
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='urlpatterns_reverse.reverse_lazy_urls')
|
||||
class ReverseLazyTest(TestCase):
|
||||
|
|
Loading…
Reference in New Issue