Skip to content

Commit

Permalink
[1.7.x] Fixed #22291 -- Avoided shadowing deadlock exceptions on MySQL.
Browse files Browse the repository at this point in the history
Thanks err for the report.

Backport of 58161e4 from master.
  • Loading branch information
aaugustin committed Apr 10, 2014
1 parent ae78227 commit 7e89434
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 2 deletions.
8 changes: 7 additions & 1 deletion django/db/transaction.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -339,7 +339,13 @@ def __exit__(self, exc_type, exc_value, traceback):
if sid is None: if sid is None:
connection.needs_rollback = True connection.needs_rollback = True
else: 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: else:
# Roll back transaction # Roll back transaction
connection.rollback() connection.rollback()
Expand Down
47 changes: 46 additions & 1 deletion tests/transactions/tests.py
Original file line number Original file line Diff line number Diff line change
@@ -1,9 +1,15 @@
from __future__ import unicode_literals from __future__ import unicode_literals


import sys import sys
try:
import threading
except ImportError:
threading = None
import time
from unittest import skipIf, skipUnless 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, skipUnlessDBFeature from django.test import TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
from django.test.utils import IgnoreDeprecationWarningsMixin from django.test.utils import IgnoreDeprecationWarningsMixin
from django.utils import six from django.utils import six
Expand Down Expand Up @@ -355,6 +361,45 @@ def test_atomic_allows_queries_after_fixing_transaction(self):
self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Calculus") 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): class AtomicMiscTests(TransactionTestCase):


available_apps = [] available_apps = []
Expand Down

0 comments on commit 7e89434

Please sign in to comment.