@@ -70,9 +70,7 @@ def head_bytes(c):
7070 stdout = os.fdopen(1, 'w')
7171 input_str = stdin.read({0})
7272 stdout.write(input_str)
73- """ .format (
74- c
75- )
73+ """ .format (c )
7674 )
7775 return cmd ("python" , "-c" , code )
7876
@@ -86,9 +84,7 @@ def echo_var(var_name):
8684 """\
8785 import os
8886 print(os.environ.get("{0}", ""))
89- """ .format (
90- var_name
91- )
87+ """ .format (var_name )
9288 )
9389 return cmd ("python" , "-c" , code )
9490
@@ -103,9 +99,7 @@ def replace(a, b):
10399 import sys
104100 input_str = sys.stdin.read()
105101 sys.stdout.write(input_str.replace({0}, {1}))
106- """ .format (
107- repr (a ), repr (b )
108- )
102+ """ .format (repr (a ), repr (b ))
109103 )
110104 return cmd ("python" , "-c" , code )
111105
@@ -684,9 +678,7 @@ def test_kill_with_grandchild():
684678print("started")
685679sys.stdout.flush()
686680p.wait()
687- """ .format (
688- grandchild_code
689- )
681+ """ .format (grandchild_code )
690682
691683 # Capturing stderr means an IO thread is spawned, even though we're using a
692684 # ReaderHandle to read stdout. What we're testing here is that kill()
@@ -1005,3 +997,39 @@ def test_zombies_reaped():
1005997 while ps_observes_pid (pid ):
1006998 tries += 1
1007999 assert tries < 100 , f"child #{ i } (PID { pid } ) never went away?"
1000+
1001+
1002+ def test_pipe_inheritance ():
1003+ waiters = []
1004+ threads = []
1005+
1006+ def reader_fn ():
1007+ # Open a pipe for the child to read from. This can deadlock if a waiter
1008+ # inherits this pipe and keeps it open.
1009+ reader , writer = os .pipe ()
1010+ os .fdopen (writer , "wb" ).write (b"foo" )
1011+ output = cat_cmd ().stdin_file (reader ).read ()
1012+ assert output == "foo"
1013+
1014+ def waiter_fn ():
1015+ # Spawn a child that won't exit until we kill it. This is intended to
1016+ # keep pipes open if they're inherited unintentionally. Without
1017+ # something like this, we'd need an inheritance *cycle* between
1018+ # readers, which might be unlikely.
1019+ waiters .append (sleep_cmd (365 * 24 * 60 * 60 ).unchecked ().start ())
1020+
1021+ for _ in range (100 ):
1022+ thread = threading .Thread (target = reader_fn )
1023+ thread .start ()
1024+ threads .append (thread )
1025+
1026+ thread = threading .Thread (target = waiter_fn )
1027+ thread .start ()
1028+ threads .append (thread )
1029+
1030+ for thread in threads :
1031+ thread .join ()
1032+
1033+ for waiter in waiters :
1034+ waiter .kill ()
1035+ waiter .wait ()
0 commit comments