Loading django/db/transaction.py +7 −1 Original line number Diff line number Diff line Loading @@ -316,7 +316,13 @@ class Atomic(object): if sid is None: connection.needs_rollback = True else: try: connection.savepoint_rollback(sid) except DatabaseError: # If rolling back to a savepoint fails, mark for # rollback at a higher level and avoid shadowing # the original exception. connection.needs_rollback = True else: # Roll back transaction connection.rollback() Loading tests/transactions/tests.py +47 −2 Original line number Diff line number Diff line from __future__ import absolute_import import sys from django.db import connection, transaction, DatabaseError, IntegrityError try: import threading except ImportError: threading = None import time from django.db import (connection, transaction, DatabaseError, IntegrityError, OperationalError) from django.test import TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import IgnorePendingDeprecationWarningsMixin from django.utils import six Loading Loading @@ -354,6 +360,45 @@ class AtomicErrorsTests(TransactionTestCase): self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Calculus") @skipUnless(connection.vendor == 'mysql', "MySQL-specific behaviors") class AtomicMySQLTests(TransactionTestCase): available_apps = ['transactions'] @skipIf(threading is None, "Test requires threading") def test_implicit_savepoint_rollback(self): """MySQL implicitly rolls back savepoints when it deadlocks (#22291).""" other_thread_ready = threading.Event() def other_thread(): try: with transaction.atomic(): Reporter.objects.create(id=1, first_name="Tintin") other_thread_ready.set() # We cannot synchronize the two threads with an event here # because the main thread locks. Sleep for a little while. time.sleep(1) # 2) ... and this line deadlocks. (see below for 1) Reporter.objects.exclude(id=1).update(id=2) finally: # This is the thread-local connection, not the main connection. connection.close() other_thread = threading.Thread(target=other_thread) other_thread.start() other_thread_ready.wait() with six.assertRaisesRegex(self, OperationalError, 'Deadlock found'): # Double atomic to enter a transaction and create a savepoint. with transaction.atomic(): with transaction.atomic(): # 1) This line locks... (see above for 2) Reporter.objects.create(id=1, first_name="Tintin") other_thread.join() class AtomicMiscTests(TransactionTestCase): available_apps = [] Loading Loading
django/db/transaction.py +7 −1 Original line number Diff line number Diff line Loading @@ -316,7 +316,13 @@ class Atomic(object): if sid is None: connection.needs_rollback = True else: try: connection.savepoint_rollback(sid) except DatabaseError: # If rolling back to a savepoint fails, mark for # rollback at a higher level and avoid shadowing # the original exception. connection.needs_rollback = True else: # Roll back transaction connection.rollback() Loading
tests/transactions/tests.py +47 −2 Original line number Diff line number Diff line from __future__ import absolute_import import sys from django.db import connection, transaction, DatabaseError, IntegrityError try: import threading except ImportError: threading = None import time from django.db import (connection, transaction, DatabaseError, IntegrityError, OperationalError) from django.test import TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import IgnorePendingDeprecationWarningsMixin from django.utils import six Loading Loading @@ -354,6 +360,45 @@ class AtomicErrorsTests(TransactionTestCase): self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Calculus") @skipUnless(connection.vendor == 'mysql', "MySQL-specific behaviors") class AtomicMySQLTests(TransactionTestCase): available_apps = ['transactions'] @skipIf(threading is None, "Test requires threading") def test_implicit_savepoint_rollback(self): """MySQL implicitly rolls back savepoints when it deadlocks (#22291).""" other_thread_ready = threading.Event() def other_thread(): try: with transaction.atomic(): Reporter.objects.create(id=1, first_name="Tintin") other_thread_ready.set() # We cannot synchronize the two threads with an event here # because the main thread locks. Sleep for a little while. time.sleep(1) # 2) ... and this line deadlocks. (see below for 1) Reporter.objects.exclude(id=1).update(id=2) finally: # This is the thread-local connection, not the main connection. connection.close() other_thread = threading.Thread(target=other_thread) other_thread.start() other_thread_ready.wait() with six.assertRaisesRegex(self, OperationalError, 'Deadlock found'): # Double atomic to enter a transaction and create a savepoint. with transaction.atomic(): with transaction.atomic(): # 1) This line locks... (see above for 2) Reporter.objects.create(id=1, first_name="Tintin") other_thread.join() class AtomicMiscTests(TransactionTestCase): available_apps = [] Loading