[1.6.x] Fixed #22291 -- Avoided shadowing deadlock exceptions on MySQL.

Thanks err for the report.

Backport of 58161e4e from master.
This commit is contained in:
Aymeric Augustin 2014-03-23 20:45:22 +01:00
parent de95867284
commit 9afedbef42
2 changed files with 53 additions and 2 deletions

View File

@ -316,7 +316,13 @@ class Atomic(object):
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 File

@ -1,8 +1,14 @@
from __future__ import absolute_import
import sys
try:
import threading
except ImportError:
threading = None
import time
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.utils import IgnorePendingDeprecationWarningsMixin
from django.utils import six
@ -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 = []