2222
2323import contextlib
2424import sqlite3 as sqlite
25+ import subprocess
2526import sys
2627import threading
2728import unittest
2829
29- from test .support import check_disallow_instantiation , threading_helper , bigmemtest
30+ from test .support import (
31+ bigmemtest ,
32+ check_disallow_instantiation ,
33+ threading_helper ,
34+ SHORT_TIMEOUT ,
35+ )
3036from test .support .os_helper import TESTFN , unlink
3137
3238
@@ -986,6 +992,77 @@ def test_on_conflict_replace(self):
986992 self .assertEqual (self .cu .fetchall (), [('Very different data!' , 'foo' )])
987993
988994
995+ class MultiprocessTests (unittest .TestCase ):
996+ CONNECTION_TIMEOUT = SHORT_TIMEOUT / 1000. # Defaults to 30 ms
997+
998+ def tearDown (self ):
999+ unlink (TESTFN )
1000+
1001+ def test_ctx_mgr_rollback_if_commit_failed (self ):
1002+ # bpo-27334: ctx manager does not rollback if commit fails
1003+ SCRIPT = f"""if 1:
1004+ import sqlite3
1005+ def wait():
1006+ print("started")
1007+ assert "database is locked" in input()
1008+
1009+ cx = sqlite3.connect("{ TESTFN } ", timeout={ self .CONNECTION_TIMEOUT } )
1010+ cx.create_function("wait", 0, wait)
1011+ with cx:
1012+ cx.execute("create table t(t)")
1013+ try:
1014+ # execute two transactions; both will try to lock the db
1015+ cx.executescript('''
1016+ -- start a transaction and wait for parent
1017+ begin transaction;
1018+ select * from t;
1019+ select wait();
1020+ rollback;
1021+
1022+ -- start a new transaction; would fail if parent holds lock
1023+ begin transaction;
1024+ select * from t;
1025+ rollback;
1026+ ''')
1027+ finally:
1028+ cx.close()
1029+ """
1030+
1031+ # spawn child process
1032+ proc = subprocess .Popen (
1033+ [sys .executable , "-c" , SCRIPT ],
1034+ encoding = "utf-8" ,
1035+ bufsize = 0 ,
1036+ stdin = subprocess .PIPE ,
1037+ stdout = subprocess .PIPE ,
1038+ )
1039+ self .addCleanup (proc .communicate )
1040+
1041+ # wait for child process to start
1042+ self .assertEqual ("started" , proc .stdout .readline ().strip ())
1043+
1044+ cx = sqlite .connect (TESTFN , timeout = self .CONNECTION_TIMEOUT )
1045+ try : # context manager should correctly release the db lock
1046+ with cx :
1047+ cx .execute ("insert into t values('test')" )
1048+ except sqlite .OperationalError as exc :
1049+ proc .stdin .write (str (exc ))
1050+ else :
1051+ proc .stdin .write ("no error" )
1052+ finally :
1053+ cx .close ()
1054+
1055+ # terminate child process
1056+ self .assertIsNone (proc .returncode )
1057+ try :
1058+ proc .communicate (input = "end" , timeout = SHORT_TIMEOUT )
1059+ except subprocess .TimeoutExpired :
1060+ proc .kill ()
1061+ proc .communicate ()
1062+ raise
1063+ self .assertEqual (proc .returncode , 0 )
1064+
1065+
9891066def suite ():
9901067 tests = [
9911068 ClosedConTests ,
@@ -995,6 +1072,7 @@ def suite():
9951072 CursorTests ,
9961073 ExtensionTests ,
9971074 ModuleTests ,
1075+ MultiprocessTests ,
9981076 OpenTests ,
9991077 SqliteOnConflictTests ,
10001078 ThreadTests ,
0 commit comments