From ae095732281cf91169e2ed82273731e5b0b92b12 Mon Sep 17 00:00:00 2001
From: Usman Khalid <2200617@gmail.com>
Date: Mon, 29 Sep 2014 16:22:28 +0500
Subject: [PATCH] commit_on_success_with_read_committed should only work with
 atmost 1 level of transactions.

TNL-367
---
 common/djangoapps/util/db.py            |  9 ++++++++-
 common/djangoapps/util/tests/test_db.py | 22 +++++++++++++++++++++-
 2 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/common/djangoapps/util/db.py b/common/djangoapps/util/db.py
index 08dd347158a..3e8354fc6ab 100644
--- a/common/djangoapps/util/db.py
+++ b/common/djangoapps/util/db.py
@@ -13,6 +13,8 @@ def commit_on_success_with_read_committed(func):  # pylint: disable=invalid-name
     If the function returns a response the transaction is committed and if the function raises an exception the
     transaction is rolled back.
 
+    Raises TransactionManagementError if there are already more than 1 level of transactions open.
+
     Note: This only works on MySQL.
     """
     @wraps(func)
@@ -21,7 +23,12 @@ def commit_on_success_with_read_committed(func):  # pylint: disable=invalid-name
         if connection.vendor == 'mysql':
             # The isolation level cannot be changed while a transaction is in progress. So we close any existing one.
             if connection.transaction_state:
-                connection.commit()
+                if len(connection.transaction_state) == 1:
+                    connection.commit()
+                # We can commit all open transactions. But it does not seem like a good idea.
+                elif len(connection.transaction_state) > 1:
+                    raise transaction.TransactionManagementError('Cannot change isolation level. '
+                                                                 'More than 1 level of nested transactions.')
 
             # This will set the transaction isolation level to READ COMMITTED for the next transaction.
             cursor = connection.cursor()
diff --git a/common/djangoapps/util/tests/test_db.py b/common/djangoapps/util/tests/test_db.py
index 5ba8a7f07a1..99687cadae1 100644
--- a/common/djangoapps/util/tests/test_db.py
+++ b/common/djangoapps/util/tests/test_db.py
@@ -7,7 +7,7 @@ import unittest
 
 from django.contrib.auth.models import User
 from django.db import connection, IntegrityError
-from django.db.transaction import commit_on_success
+from django.db.transaction import commit_on_success, TransactionManagementError
 from django.test import TransactionTestCase
 
 from util.db import commit_on_success_with_read_committed
@@ -79,3 +79,23 @@ class TransactionIsolationLevelsTestCase(TransactionTestCase):
 
         self.assertIsNone(thread2.status.get('exception'))
         self.assertEqual(thread2.status.get('created'), created_in_2)
+
+    def test_transaction_nesting(self):
+        """Test that the decorator raises an error if there are already more than 1 levels of nested transactions."""
+
+        if connection.vendor != 'mysql':
+            raise unittest.SkipTest('Only works on MySQL.')
+
+        def do_nothing():
+            """Just return."""
+            return
+
+        commit_on_success_with_read_committed(do_nothing)()
+
+        with commit_on_success():
+            commit_on_success_with_read_committed(do_nothing)()
+
+        with self.assertRaises(TransactionManagementError):
+            with commit_on_success():
+                with commit_on_success():
+                    commit_on_success_with_read_committed(do_nothing)()
-- 
GitLab