Loading django/db/transaction.py +7 −1 Original line number Diff line number Diff line Loading @@ -231,7 +231,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 +46 −1 Original line number Diff line number Diff line from __future__ import unicode_literals import sys try: import threading except ImportError: threading = None import time from unittest import skipIf, skipUnless from django.db import connection, transaction, DatabaseError, IntegrityError from django.db import (connection, transaction, DatabaseError, IntegrityError, OperationalError) from django.test import TransactionTestCase, skipIfDBFeature from django.utils import six Loading Loading @@ -333,6 +339,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 @@ -231,7 +231,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 +46 −1 Original line number Diff line number Diff line from __future__ import unicode_literals import sys try: import threading except ImportError: threading = None import time from unittest import skipIf, skipUnless from django.db import connection, transaction, DatabaseError, IntegrityError from django.db import (connection, transaction, DatabaseError, IntegrityError, OperationalError) from django.test import TransactionTestCase, skipIfDBFeature from django.utils import six Loading Loading @@ -333,6 +339,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