diff options
| author | Jon Dufresne <jon.dufresne@gmail.com> | 2019-02-14 07:04:55 -0800 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2019-02-14 10:05:13 -0500 |
| commit | 37cc6a9dce3354cd37f23ee972bc25b0e5cebd5c (patch) | |
| tree | 90f49316a7b3104b924c5556491ba56e803b04f2 /django | |
| parent | 07b44a251a41ca93a7f5593761fcf808249665f0 (diff) | |
[2.2.x] Fixed #30171 -- Fixed DatabaseError in servers tests.
Made DatabaseWrapper thread sharing logic reentrant. Used a reference
counting like scheme to allow nested uses.
The error appeared after 8c775391b78b2a4a2b57c5e89ed4888f36aada4b.
Backport of 76990cbbda5d93fda560c8a5ab019860f7efaab7 from master.
Diffstat (limited to 'django')
| -rw-r--r-- | django/db/backends/base/base.py | 38 | ||||
| -rw-r--r-- | django/db/backends/postgresql/base.py | 1 | ||||
| -rw-r--r-- | django/test/testcases.py | 9 |
3 files changed, 28 insertions, 20 deletions
diff --git a/django/db/backends/base/base.py b/django/db/backends/base/base.py index f97d171c96..9fa03cc0ee 100644 --- a/django/db/backends/base/base.py +++ b/django/db/backends/base/base.py @@ -1,4 +1,5 @@ import copy +import threading import time import warnings from collections import deque @@ -43,8 +44,7 @@ class BaseDatabaseWrapper: queries_limit = 9000 - def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS, - allow_thread_sharing=False): + def __init__(self, settings_dict, alias=DEFAULT_DB_ALIAS): # Connection related attributes. # The underlying database connection. self.connection = None @@ -80,7 +80,8 @@ class BaseDatabaseWrapper: self.errors_occurred = False # Thread-safety related attributes. - self.allow_thread_sharing = allow_thread_sharing + self._thread_sharing_lock = threading.Lock() + self._thread_sharing_count = 0 self._thread_ident = _thread.get_ident() # A list of no-argument functions to run when the transaction commits. @@ -515,12 +516,27 @@ class BaseDatabaseWrapper: # ##### Thread safety handling ##### + @property + def allow_thread_sharing(self): + with self._thread_sharing_lock: + return self._thread_sharing_count > 0 + + def inc_thread_sharing(self): + with self._thread_sharing_lock: + self._thread_sharing_count += 1 + + def dec_thread_sharing(self): + with self._thread_sharing_lock: + if self._thread_sharing_count <= 0: + raise RuntimeError('Cannot decrement the thread sharing count below zero.') + self._thread_sharing_count -= 1 + def validate_thread_sharing(self): """ Validate that the connection isn't accessed by another thread than the one which originally created it, unless the connection was explicitly - authorized to be shared between threads (via the `allow_thread_sharing` - property). Raise an exception if the validation fails. + authorized to be shared between threads (via the `inc_thread_sharing()` + method). Raise an exception if the validation fails. """ if not (self.allow_thread_sharing or self._thread_ident == _thread.get_ident()): raise DatabaseError( @@ -589,11 +605,7 @@ class BaseDatabaseWrapper: potential child threads while (or after) the test database is destroyed. Refs #10868, #17786, #16969. """ - return self.__class__( - {**self.settings_dict, 'NAME': None}, - alias=NO_DB_ALIAS, - allow_thread_sharing=False, - ) + return self.__class__({**self.settings_dict, 'NAME': None}, alias=NO_DB_ALIAS) def schema_editor(self, *args, **kwargs): """ @@ -635,7 +647,7 @@ class BaseDatabaseWrapper: finally: self.execute_wrappers.pop() - def copy(self, alias=None, allow_thread_sharing=None): + def copy(self, alias=None): """ Return a copy of this connection. @@ -644,6 +656,4 @@ class BaseDatabaseWrapper: settings_dict = copy.deepcopy(self.settings_dict) if alias is None: alias = self.alias - if allow_thread_sharing is None: - allow_thread_sharing = self.allow_thread_sharing - return type(self)(settings_dict, alias, allow_thread_sharing) + return type(self)(settings_dict, alias) diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index e9db668a4d..4376b8609c 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -277,7 +277,6 @@ class DatabaseWrapper(BaseDatabaseWrapper): return self.__class__( {**self.settings_dict, 'NAME': connection.settings_dict['NAME']}, alias=self.alias, - allow_thread_sharing=False, ) return nodb_connection diff --git a/django/test/testcases.py b/django/test/testcases.py index 991165c04d..dea7fedbcc 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1442,7 +1442,7 @@ class LiveServerTestCase(TransactionTestCase): # the server thread. if conn.vendor == 'sqlite' and conn.is_in_memory_db(): # Explicitly enable thread-shareability for this connection - conn.allow_thread_sharing = True + conn.inc_thread_sharing() connections_override[conn.alias] = conn cls._live_server_modified_settings = modify_settings( @@ -1478,10 +1478,9 @@ class LiveServerTestCase(TransactionTestCase): # Terminate the live server's thread cls.server_thread.terminate() - # Restore sqlite in-memory database connections' non-shareability - for conn in connections.all(): - if conn.vendor == 'sqlite' and conn.is_in_memory_db(): - conn.allow_thread_sharing = False + # Restore sqlite in-memory database connections' non-shareability. + for conn in cls.server_thread.connections_override.values(): + conn.dec_thread_sharing() @classmethod def tearDownClass(cls): |
