Fixed #22291 -- Avoided shadowing deadlock exceptions on MySQL.
Thanks err for the report.
This commit is contained in:
parent
a6fc18594e
commit
58161e4e93
|
@ -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()
|
||||
|
|
|
@ -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 @@ 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…
Reference in New Issue