summaryrefslogtreecommitdiffstats
path: root/Lib/sqlite3
diff options
context:
space:
mode:
authorErlend Egeberg Aasland <erlend.aasland@innova.no>2021-08-25 10:59:42 (GMT)
committerGitHub <noreply@github.com>2021-08-25 10:59:42 (GMT)
commit7ecd3425d45a37efbc745788dfe21df0286c785a (patch)
tree5480bf4db64fadf5ad4e2e931464c8278443ea28 /Lib/sqlite3
parent3df0fc89bc2714f5ef03e36a926bc795dcd5e05a (diff)
downloadcpython-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.py80
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,