Skip to content

Commit 66f1983

Browse files
committed
Fix race in test_process_delayed_stdio__not_paused__no_stdin
transport._wait() only waits for the child to be reaped (SIGCHLD), not for its stdout pipe to be drained and closed. Process exit and pipe EOF reach libuv through independent kernel mechanisms and can be observed in different event loop iterations, so the test could assert on proto.stages before connection_lost had actually fired, sometimes leaving the pipe/process transports alive past the end of the test. Wait on the protocol's own connection_lost instead, which uvloop only fires once the process has exited and all pipes have disconnected.
1 parent e271aab commit 66f1983

1 file changed

Lines changed: 13 additions & 1 deletion

File tree

tests/test_process.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,7 @@ class TestProto:
887887
def __init__(self):
888888
self.lost = 0
889889
self.stages = []
890+
self.connection_lost_fut = asyncio.Future()
890891

891892
def connection_made(self, transport):
892893
self.stages.append(('CM', transport))
@@ -905,6 +906,8 @@ def process_exited(self):
905906
def connection_lost(self, exc):
906907
self.stages.append(('CL', self.lost, exc))
907908
self.lost += 1
909+
if not self.connection_lost_fut.done():
910+
self.connection_lost_fut.set_result(None)
908911

909912
async def run_sub(self, **kwargs):
910913
return await self.loop.subprocess_shell(
@@ -962,7 +965,16 @@ def test_process_delayed_stdio__not_paused__no_stdin(self):
962965
stdin=None,
963966
stdout=subprocess.PIPE,
964967
stderr=subprocess.PIPE))
965-
self.loop.run_until_complete(transport._wait())
968+
# transport._wait() only waits for the child process to be reaped
969+
# (SIGCHLD/waitpid); it says nothing about whether the stdout pipe
970+
# has been drained and closed yet. Process exit and pipe EOF are
971+
# delivered to libuv via independent kernel mechanisms (a signal
972+
# and epoll readiness, respectively), so they can be observed in
973+
# separate event loop iterations. Waiting on the transport's own
974+
# connection_lost (which uvloop only fires once *both* the process
975+
# has exited *and* all pipes have disconnected) avoids asserting on
976+
# proto.stages before that sequence has actually finished.
977+
self.loop.run_until_complete(proto.connection_lost_fut)
966978
self.assertEqual(transport.get_returncode(), 0)
967979
self.assertIsNot(transport, None)
968980
self.assertEqual(

0 commit comments

Comments
 (0)