mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +00:00
[3.10] bpo-27334: roll back transaction if sqlite3 context manager fails to commit (GH-26202) (GH-27943)
This commit is contained in:
parent
81fa08c5ea
commit
2a80893e5c
3 changed files with 97 additions and 7 deletions
|
@ -20,12 +20,13 @@
|
|||
# misrepresented as being the original software.
|
||||
# 3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
import subprocess
|
||||
import threading
|
||||
import unittest
|
||||
import sqlite3 as sqlite
|
||||
import sys
|
||||
|
||||
from test.support import check_disallow_instantiation
|
||||
from test.support import check_disallow_instantiation, SHORT_TIMEOUT
|
||||
from test.support.os_helper import TESTFN, unlink
|
||||
|
||||
|
||||
|
@ -958,6 +959,77 @@ class SqliteOnConflictTests(unittest.TestCase):
|
|||
self.assertEqual(self.cu.fetchall(), [('Very different data!', 'foo')])
|
||||
|
||||
|
||||
class MultiprocessTests(unittest.TestCase):
|
||||
CONNECTION_TIMEOUT = SHORT_TIMEOUT / 1000. # Defaults to 30 ms
|
||||
|
||||
def tearDown(self):
|
||||
unlink(TESTFN)
|
||||
|
||||
def test_ctx_mgr_rollback_if_commit_failed(self):
|
||||
# bpo-27334: ctx manager does not rollback if commit fails
|
||||
SCRIPT = f"""if 1:
|
||||
import sqlite3
|
||||
def wait():
|
||||
print("started")
|
||||
assert "database is locked" in input()
|
||||
|
||||
cx = sqlite3.connect("{TESTFN}", timeout={self.CONNECTION_TIMEOUT})
|
||||
cx.create_function("wait", 0, wait)
|
||||
with cx:
|
||||
cx.execute("create table t(t)")
|
||||
try:
|
||||
# execute two transactions; both will try to lock the db
|
||||
cx.executescript('''
|
||||
-- start a transaction and wait for parent
|
||||
begin transaction;
|
||||
select * from t;
|
||||
select wait();
|
||||
rollback;
|
||||
|
||||
-- start a new transaction; would fail if parent holds lock
|
||||
begin transaction;
|
||||
select * from t;
|
||||
rollback;
|
||||
''')
|
||||
finally:
|
||||
cx.close()
|
||||
"""
|
||||
|
||||
# spawn child process
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, "-c", SCRIPT],
|
||||
encoding="utf-8",
|
||||
bufsize=0,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
self.addCleanup(proc.communicate)
|
||||
|
||||
# wait for child process to start
|
||||
self.assertEqual("started", proc.stdout.readline().strip())
|
||||
|
||||
cx = sqlite.connect(TESTFN, timeout=self.CONNECTION_TIMEOUT)
|
||||
try: # context manager should correctly release the db lock
|
||||
with cx:
|
||||
cx.execute("insert into t values('test')")
|
||||
except sqlite.OperationalError as exc:
|
||||
proc.stdin.write(str(exc))
|
||||
else:
|
||||
proc.stdin.write("no error")
|
||||
finally:
|
||||
cx.close()
|
||||
|
||||
# terminate child process
|
||||
self.assertIsNone(proc.returncode)
|
||||
try:
|
||||
proc.communicate(input="end", timeout=SHORT_TIMEOUT)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
proc.communicate()
|
||||
raise
|
||||
self.assertEqual(proc.returncode, 0)
|
||||
|
||||
|
||||
def suite():
|
||||
tests = [
|
||||
ClosedConTests,
|
||||
|
@ -967,6 +1039,7 @@ def suite():
|
|||
CursorTests,
|
||||
ExtensionTests,
|
||||
ModuleTests,
|
||||
MultiprocessTests,
|
||||
SqliteOnConflictTests,
|
||||
ThreadTests,
|
||||
UninitialisedConnectionTests,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue