diff options
author | Erlend Egeberg Aasland <erlend.aasland@innova.no> | 2021-08-25 10:59:42 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-25 10:59:42 (GMT) |
commit | 7ecd3425d45a37efbc745788dfe21df0286c785a (patch) | |
tree | 5480bf4db64fadf5ad4e2e931464c8278443ea28 /Lib/sqlite3 | |
parent | 3df0fc89bc2714f5ef03e36a926bc795dcd5e05a (diff) | |
download | cpython-7ecd3425d45a37efbc745788dfe21df0286c785a.zip cpython-7ecd3425d45a37efbc745788dfe21df0286c785a.tar.gz cpython-7ecd3425d45a37efbc745788dfe21df0286c785a.tar.bz2 |
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>
Diffstat (limited to 'Lib/sqlite3')
-rw-r--r-- | Lib/sqlite3/test/dbapi.py | 80 |
1 files changed, 79 insertions, 1 deletions
diff --git a/Lib/sqlite3/test/dbapi.py b/Lib/sqlite3/test/dbapi.py index 5d7e5bb..bb9d5a7 100644 --- a/Lib/sqlite3/test/dbapi.py +++ b/Lib/sqlite3/test/dbapi.py @@ -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, |