2020# misrepresented as being the original software.
2121# 3. This notice may not be removed or altered from any source distribution.
2222
23+ import subprocess
2324import threading
2425import unittest
2526import sqlite3 as sqlite
2627import sys
2728
28- from test .support import check_disallow_instantiation
29+ from test .support import check_disallow_instantiation , SHORT_TIMEOUT
2930from test .support .os_helper import TESTFN , unlink
3031
3132
@@ -958,6 +959,77 @@ def test_on_conflict_replace(self):
958959 self .assertEqual (self .cu .fetchall (), [('Very different data!' , 'foo' )])
959960
960961
962+ class MultiprocessTests (unittest .TestCase ):
963+ CONNECTION_TIMEOUT = SHORT_TIMEOUT / 1000. # Defaults to 30 ms
964+
965+ def tearDown (self ):
966+ unlink (TESTFN )
967+
968+ def test_ctx_mgr_rollback_if_commit_failed (self ):
969+ # bpo-27334: ctx manager does not rollback if commit fails
970+ SCRIPT = f"""if 1:
971+ import sqlite3
972+ def wait():
973+ print("started")
974+ assert "database is locked" in input()
975+
976+ cx = sqlite3.connect("{ TESTFN } ", timeout={ self .CONNECTION_TIMEOUT } )
977+ cx.create_function("wait", 0, wait)
978+ with cx:
979+ cx.execute("create table t(t)")
980+ try:
981+ # execute two transactions; both will try to lock the db
982+ cx.executescript('''
983+ -- start a transaction and wait for parent
984+ begin transaction;
985+ select * from t;
986+ select wait();
987+ rollback;
988+
989+ -- start a new transaction; would fail if parent holds lock
990+ begin transaction;
991+ select * from t;
992+ rollback;
993+ ''')
994+ finally:
995+ cx.close()
996+ """
997+
998+ # spawn child process
999+ proc = subprocess .Popen (
1000+ [sys .executable , "-c" , SCRIPT ],
1001+ encoding = "utf-8" ,
1002+ bufsize = 0 ,
1003+ stdin = subprocess .PIPE ,
1004+ stdout = subprocess .PIPE ,
1005+ )
1006+ self .addCleanup (proc .communicate )
1007+
1008+ # wait for child process to start
1009+ self .assertEqual ("started" , proc .stdout .readline ().strip ())
1010+
1011+ cx = sqlite .connect (TESTFN , timeout = self .CONNECTION_TIMEOUT )
1012+ try : # context manager should correctly release the db lock
1013+ with cx :
1014+ cx .execute ("insert into t values('test')" )
1015+ except sqlite .OperationalError as exc :
1016+ proc .stdin .write (str (exc ))
1017+ else :
1018+ proc .stdin .write ("no error" )
1019+ finally :
1020+ cx .close ()
1021+
1022+ # terminate child process
1023+ self .assertIsNone (proc .returncode )
1024+ try :
1025+ proc .communicate (input = "end" , timeout = SHORT_TIMEOUT )
1026+ except subprocess .TimeoutExpired :
1027+ proc .kill ()
1028+ proc .communicate ()
1029+ raise
1030+ self .assertEqual (proc .returncode , 0 )
1031+
1032+
9611033def suite ():
9621034 tests = [
9631035 ClosedConTests ,
@@ -967,6 +1039,7 @@ def suite():
9671039 CursorTests ,
9681040 ExtensionTests ,
9691041 ModuleTests ,
1042+ MultiprocessTests ,
9701043 SqliteOnConflictTests ,
9711044 ThreadTests ,
9721045 UninitialisedConnectionTests ,
0 commit comments