2121# misrepresented as being the original software.
2222# 3. This notice may not be removed or altered from any source distribution.
2323
24+ import subprocess
2425import threading
2526import unittest
2627import sqlite3 as sqlite
28+ import sys
2729
28- from test .support import TESTFN , unlink
30+ from test .support import SHORT_TIMEOUT , TESTFN , unlink
2931
3032
3133class ModuleTests (unittest .TestCase ):
@@ -954,6 +956,77 @@ def CheckOnConflictReplace(self):
954956 self .assertEqual (self .cu .fetchall (), [('Very different data!' , 'foo' )])
955957
956958
959+ class MultiprocessTests (unittest .TestCase ):
960+ CONNECTION_TIMEOUT = SHORT_TIMEOUT / 1000. # Defaults to 30 ms
961+
962+ def tearDown (self ):
963+ unlink (TESTFN )
964+
965+ def test_ctx_mgr_rollback_if_commit_failed (self ):
966+ # bpo-27334: ctx manager does not rollback if commit fails
967+ SCRIPT = f"""if 1:
968+ import sqlite3
969+ def wait():
970+ print("started")
971+ assert "database is locked" in input()
972+
973+ cx = sqlite3.connect("{ TESTFN } ", timeout={ self .CONNECTION_TIMEOUT } )
974+ cx.create_function("wait", 0, wait)
975+ with cx:
976+ cx.execute("create table t(t)")
977+ try:
978+ # execute two transactions; both will try to lock the db
979+ cx.executescript('''
980+ -- start a transaction and wait for parent
981+ begin transaction;
982+ select * from t;
983+ select wait();
984+ rollback;
985+
986+ -- start a new transaction; would fail if parent holds lock
987+ begin transaction;
988+ select * from t;
989+ rollback;
990+ ''')
991+ finally:
992+ cx.close()
993+ """
994+
995+ # spawn child process
996+ proc = subprocess .Popen (
997+ [sys .executable , "-c" , SCRIPT ],
998+ encoding = "utf-8" ,
999+ bufsize = 0 ,
1000+ stdin = subprocess .PIPE ,
1001+ stdout = subprocess .PIPE ,
1002+ )
1003+ self .addCleanup (proc .communicate )
1004+
1005+ # wait for child process to start
1006+ self .assertEqual ("started" , proc .stdout .readline ().strip ())
1007+
1008+ cx = sqlite .connect (TESTFN , timeout = self .CONNECTION_TIMEOUT )
1009+ try : # context manager should correctly release the db lock
1010+ with cx :
1011+ cx .execute ("insert into t values('test')" )
1012+ except sqlite .OperationalError as exc :
1013+ proc .stdin .write (str (exc ))
1014+ else :
1015+ proc .stdin .write ("no error" )
1016+ finally :
1017+ cx .close ()
1018+
1019+ # terminate child process
1020+ self .assertIsNone (proc .returncode )
1021+ try :
1022+ proc .communicate (input = "end" , timeout = SHORT_TIMEOUT )
1023+ except subprocess .TimeoutExpired :
1024+ proc .kill ()
1025+ proc .communicate ()
1026+ raise
1027+ self .assertEqual (proc .returncode , 0 )
1028+
1029+
9571030def suite ():
9581031 module_suite = unittest .makeSuite (ModuleTests , "Check" )
9591032 connection_suite = unittest .makeSuite (ConnectionTests , "Check" )
@@ -965,10 +1038,11 @@ def suite():
9651038 closed_cur_suite = unittest .makeSuite (ClosedCurTests , "Check" )
9661039 on_conflict_suite = unittest .makeSuite (SqliteOnConflictTests , "Check" )
9671040 uninit_con_suite = unittest .makeSuite (UninitialisedConnectionTests )
1041+ multiproc_con_suite = unittest .makeSuite (MultiprocessTests )
9681042 return unittest .TestSuite ((
9691043 module_suite , connection_suite , cursor_suite , thread_suite ,
9701044 constructor_suite , ext_suite , closed_con_suite , closed_cur_suite ,
971- on_conflict_suite , uninit_con_suite ,
1045+ on_conflict_suite , uninit_con_suite , multiproc_con_suite ,
9721046 ))
9731047
9741048def test ():
0 commit comments