@@ -125,7 +125,7 @@ def test_proc_exited_no_invalid_state_error_on_exit_waiters(self):
125125
126126 # Simulate a waiter registered via _wait() before the process exits.
127127 exit_waiter = self .loop .create_future ()
128- transport ._exit_waiters .append (exit_waiter )
128+ transport ._exit_waiters .add (exit_waiter )
129129
130130 # _connect_pipes hasn't completed, so _pipes_connected is False.
131131 self .assertFalse (transport ._pipes_connected )
@@ -910,6 +910,137 @@ async def main():
910910
911911 self .loop .run_until_complete (main ())
912912
913+ def test_communicate_cancellation_kills_process (self ):
914+ async def run ():
915+ proc = await asyncio .create_subprocess_exec (
916+ * PROGRAM_BLOCKED ,
917+ stdout = subprocess .PIPE ,
918+ )
919+ with self .assertRaises (asyncio .TimeoutError ):
920+ await asyncio .wait_for (proc .communicate (), 0.1 )
921+
922+ returncode = await asyncio .wait_for (proc .wait (), 5.0 )
923+ self .assertIsNotNone (returncode )
924+
925+ self .loop .run_until_complete (run ())
926+
927+ def test_communicate_cancellation_closes_stdout_transport (self ):
928+ async def run ():
929+ proc = await asyncio .create_subprocess_exec (
930+ * PROGRAM_BLOCKED ,
931+ stdout = subprocess .PIPE ,
932+ )
933+ try :
934+ with self .assertRaises (asyncio .TimeoutError ):
935+ await asyncio .wait_for (proc .communicate (), 0.1 )
936+
937+ await asyncio .sleep (0 )
938+
939+ stdout_transport = proc ._transport .get_pipe_transport (1 )
940+ self .assertTrue (
941+ stdout_transport is None or stdout_transport .is_closing (),
942+ "stdout pipe transport not closed after cancellation" )
943+ finally :
944+ if proc .returncode is None :
945+ proc .kill ()
946+ await proc .wait ()
947+
948+ self .loop .run_until_complete (run ())
949+
950+ def test_communicate_cancellation_closes_stdin (self ):
951+ async def run ():
952+ proc = await asyncio .create_subprocess_exec (
953+ * PROGRAM_BLOCKED ,
954+ stdin = subprocess .PIPE ,
955+ stdout = subprocess .PIPE ,
956+ )
957+ try :
958+ large_input = b'x' * (1024 * 1024 )
959+ with self .assertRaises (asyncio .TimeoutError ):
960+ await asyncio .wait_for (
961+ proc .communicate (large_input ), 0.5 )
962+
963+ await asyncio .sleep (0 )
964+
965+ stdin_transport = proc ._transport .get_pipe_transport (0 )
966+ self .assertTrue (
967+ stdin_transport is None or stdin_transport .is_closing (),
968+ "stdin pipe transport not closed after cancellation" )
969+ finally :
970+ if proc .returncode is None :
971+ proc .kill ()
972+ await proc .wait ()
973+
974+ self .loop .run_until_complete (run ())
975+
976+ def test_communicate_cancellation_closes_stderr_transport (self ):
977+ async def run ():
978+ proc = await asyncio .create_subprocess_exec (
979+ * PROGRAM_BLOCKED ,
980+ stderr = subprocess .PIPE ,
981+ )
982+ try :
983+ with self .assertRaises (asyncio .TimeoutError ):
984+ await asyncio .wait_for (proc .communicate (), 0.1 )
985+
986+ await asyncio .sleep (0 )
987+
988+ stderr_transport = proc ._transport .get_pipe_transport (2 )
989+ self .assertTrue (
990+ stderr_transport is None or stderr_transport .is_closing (),
991+ "stderr pipe transport not closed after cancellation" )
992+ finally :
993+ if proc .returncode is None :
994+ proc .kill ()
995+ await proc .wait ()
996+
997+ self .loop .run_until_complete (run ())
998+
999+ def test_wait_cancellation_removes_exit_waiters (self ):
1000+ async def run ():
1001+ proc = await asyncio .create_subprocess_exec (* PROGRAM_BLOCKED )
1002+ try :
1003+ for _ in range (5 ):
1004+ task = self .loop .create_task (proc .wait ())
1005+ self .loop .call_soon (task .cancel )
1006+ try :
1007+ await task
1008+ except asyncio .CancelledError :
1009+ pass
1010+
1011+ self .assertEqual (len (proc ._transport ._exit_waiters ), 0 )
1012+ finally :
1013+ proc .kill ()
1014+ await proc .wait ()
1015+
1016+ self .loop .run_until_complete (run ())
1017+
1018+ def test_communicate_cancellation_all_pipes (self ):
1019+ async def run ():
1020+ proc = await asyncio .create_subprocess_exec (
1021+ * PROGRAM_BLOCKED ,
1022+ stdin = subprocess .PIPE ,
1023+ stdout = subprocess .PIPE ,
1024+ stderr = subprocess .PIPE ,
1025+ )
1026+ large_input = b'x' * (1024 * 1024 )
1027+ with self .assertRaises (asyncio .TimeoutError ):
1028+ await asyncio .wait_for (
1029+ proc .communicate (large_input ), 0.5 )
1030+
1031+ await asyncio .sleep (0 )
1032+
1033+ for fd , name in [(0 , 'stdin' ), (1 , 'stdout' ), (2 , 'stderr' )]:
1034+ transport = proc ._transport .get_pipe_transport (fd )
1035+ self .assertTrue (
1036+ transport is None or transport .is_closing (),
1037+ f"{ name } pipe transport not closed after cancellation" )
1038+
1039+ returncode = await asyncio .wait_for (proc .wait (), 5.0 )
1040+ self .assertIsNotNone (returncode )
1041+
1042+ self .loop .run_until_complete (run ())
1043+
9131044 @warnings_helper .ignore_warnings (category = ResourceWarning )
9141045 def test_subprocess_read_pipe_cancelled (self ):
9151046 async def main ():
0 commit comments