mirror of
https://github.com/python/cpython
synced 2026-09-29 12:10:30 +03:00
bpo-27334: roll back transaction if sqlite3 context manager fails to commit (GH-26202)
Co-authored-by: Luca Citi Co-authored-by: Berker Peksag <berker.peksag@gmail.com>
This commit is contained in:
parent
3df0fc89bc
commit
7ecd3425d4
3 changed files with 101 additions and 7 deletions
|
|
@ -22,11 +22,17 @@
|
|||
|
||||
import contextlib
|
||||
import sqlite3 as sqlite
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import unittest
|
||||
|
||||
from test.support import check_disallow_instantiation, threading_helper, bigmemtest
|
||||
from test.support import (
|
||||
bigmemtest,
|
||||
check_disallow_instantiation,
|
||||
threading_helper,
|
||||
SHORT_TIMEOUT,
|
||||
)
|
||||
from test.support.os_helper import TESTFN, unlink
|
||||
|
||||
|
||||
|
|
@ -986,6 +992,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,
|
||||
|
|
@ -995,6 +1072,7 @@ def suite():
|
|||
CursorTests,
|
||||
ExtensionTests,
|
||||
ModuleTests,
|
||||
MultiprocessTests,
|
||||
OpenTests,
|
||||
SqliteOnConflictTests,
|
||||
ThreadTests,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue