From 76990cbbda5d93fda560c8a5ab019860f7efaab7 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 14 Feb 2019 07:04:55 -0800 Subject: 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. --- django/db/backends/base/base.py | 38 ++++++++++++++++++++++------------- django/db/backends/postgresql/base.py | 1 - 2 files changed, 24 insertions(+), 15 deletions(-) (limited to 'django/db/backends') 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 4be5a193bb..b3a3202c90 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 -- cgit v1.3