mirror of
https://github.com/django/django.git
synced 2025-10-31 09:41:08 +00:00
[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 after8c775391b7. Backport of76990cbbdafrom master.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user