Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #22291 -- Avoided shadowing deadlock exceptions on MySQL.

Thanks err for the report.
  • Loading branch information...
commit 58161e4e93d1fc796fc804c943028d4433f4813c 1 parent a6fc185
@aaugustin aaugustin authored
Showing with 53 additions and 2 deletions.
  1. +7 −1 django/db/transaction.py
  2. +46 −1 tests/transactions/tests.py
View
8 django/db/transaction.py
@@ -231,7 +231,13 @@ def __exit__(self, exc_type, exc_value, traceback):
if sid is None:
connection.needs_rollback = True
else:
- connection.savepoint_rollback(sid)
+ 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()
View
47 tests/transactions/tests.py
@@ -1,9 +1,15 @@
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
@@ -333,6 +339,45 @@ def test_atomic_allows_queries_after_fixing_transaction(self):
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 = []
Please sign in to comment.
Something went wrong with that request. Please try again.