Skip to content

Commit cacfd5a

Browse files
authored
Merge pull request #14 from skip-pay/tda/properly_close_connection_on_precommit_error
Properly close transaction in case of exception in precommit callbacks
2 parents 5b0bbc8 + 7185390 commit cacfd5a

File tree

2 files changed

+34
-3
lines changed

2 files changed

+34
-3
lines changed

chamber/patch.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import codecs
2+
import logging
3+
import sys
24

35
from django.db.models import Model
46
from django.db.models.fields import Field
@@ -7,6 +9,9 @@
79
from chamber.utils import remove_accent
810

911

12+
log = logging.getLogger(__name__)
13+
14+
1015
class OptionsLazy:
1116

1217
def __init__(self, name, klass):
@@ -100,7 +105,12 @@ def atomic_pre_commit_exit(self, exc_type, exc_value, traceback):
100105
# No exception and no rollback, pre_commit hooks can be performed on top level atomic block exit function
101106
while connection.run_pre_commit:
102107
sids, callable_hash, func = connection.run_pre_commit.pop(0)
103-
func()
108+
try:
109+
func()
110+
except Exception:
111+
log.exception("Raised error during pre_commit")
112+
exc_type, exc_value, traceback = sys.exc_info()
113+
break
104114
else:
105115
if connection.savepoint_ids:
106116
sid = connection.savepoint_ids[-1]

example/dj/apps/test_chamber/tests/transaction.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from django.test import TransactionTestCase
22
from django.db import transaction
33

4-
from django.db.transaction import on_commit
4+
from django.db.transaction import on_commit, get_connection
55

6-
from chamber.utils.transaction import UniquePreCommitCallable, in_atomic_block, pre_commit, smart_atomic
6+
from chamber.utils.transaction import UniquePreCommitCallable, pre_commit, smart_atomic
77

88
from test_chamber.models import TestSmartModel
99

@@ -19,6 +19,10 @@ def add_number(numbers_list, number):
1919
numbers_list.append(number)
2020

2121

22+
def raise_exc():
23+
raise Exception()
24+
25+
2226
class TransactionsTestCase(TransactionTestCase):
2327

2428
def test_pre_commit_without_atomic_should_be_called_immediately(self):
@@ -130,6 +134,23 @@ def pre_commit_fn_c():
130134

131135
assert_equal(data, ['a', 'b', 'c'])
132136

137+
def test_transaction_is_properly_closed_if_pre_commit_fails(self):
138+
numbers_list = []
139+
140+
with transaction.atomic():
141+
on_commit(lambda: add_number(numbers_list, 2))
142+
pre_commit(lambda: raise_exc())
143+
assert_equal(numbers_list, [])
144+
145+
# Check that another transaction is functional
146+
with transaction.atomic():
147+
connection = get_connection()
148+
with connection.cursor() as cursor:
149+
cursor.execute("SELECT 1")
150+
on_commit(lambda: add_number(numbers_list, 2))
151+
pre_commit(lambda: add_number(numbers_list, 1))
152+
assert_equal(numbers_list, [1, 2])
153+
133154
def test_chamber_atomic_should_ignore_errors(self):
134155
with assert_raises(RuntimeError):
135156
with smart_atomic():

0 commit comments

Comments
 (0)