From 5ed1db799140b1643b802d3eba26002b0ef0e8e4 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Tue, 16 Dec 2025 11:32:22 +0000 Subject: [PATCH] WIP --- .github/workflows/tests.yml | 39 ------------------------------------- mitogen/parent.py | 17 ++++++++++++---- tests/first_stage_test.py | 36 ++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7d91954a..bb01aabf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,12 +25,6 @@ jobs: fail-fast: false matrix: include: - - tox_env: py27-m_ans-ans2.10 - - tox_env: py27-m_ans-ans4 - - - tox_env: py36-m_ans-ans2.10 - - tox_env: py36-m_ans-ans4 - - tox_env: py27-m_mtg - tox_env: py36-m_mtg @@ -86,34 +80,6 @@ jobs: fail-fast: false matrix: include: - - tox_env: py311-m_ans-ans2.10 - python_version: '3.11' - - tox_env: py311-m_ans-ans3 - python_version: '3.11' - - tox_env: py311-m_ans-ans4 - python_version: '3.11' - - tox_env: py311-m_ans-ans5 - python_version: '3.11' - - tox_env: py313-m_ans-ans6 - python_version: '3.13' - - tox_env: py313-m_ans-ans7 - python_version: '3.13' - - tox_env: py313-m_ans-ans8 - python_version: '3.13' - - tox_env: py314-m_ans-ans9 - python_version: '3.14' - - tox_env: py314-m_ans-ans10 - python_version: '3.14' - - tox_env: py314-m_ans-ans11 - python_version: '3.14' - - tox_env: py314-m_ans-ans12 - python_version: '3.14' - - tox_env: py314-m_ans-ans13 - python_version: '3.14' - - - tox_env: py314-m_ans-ans13-s_lin - python_version: '3.14' - - tox_env: py314-m_mtg python_version: '3.14' @@ -161,11 +127,6 @@ jobs: fail-fast: false matrix: include: - - tox_env: py314-m_lcl-ans13 - python_version: '3.14' - - tox_env: py314-m_lcl-ans13-s_lin - python_version: '3.14' - - tox_env: py314-m_mtg python_version: '3.14' diff --git a/mitogen/parent.py b/mitogen/parent.py index e91504ce..584616b8 100644 --- a/mitogen/parent.py +++ b/mitogen/parent.py @@ -1415,11 +1415,11 @@ class Connection(object): # W: write side of interpreter stdin. # r: read side of core_src FD. # w: write side of core_src FD. - - # Final os.close(STDOUT_FILENO) to avoid --py-debug build corrupting stream with - # "[1234 refs]" during exit. @staticmethod def _first_stage(): + # Bail out in case STDIN or STDOUT is not accessible (e.g. closed) + #os.fstat(0) + #os.fstat(1) R,W=os.pipe() r,w=os.pipe() if os.fork(): @@ -1436,11 +1436,16 @@ class Connection(object): os.environ['ARGV0']=sys.executable os.execl(sys.executable,sys.executable+'(mitogen:%s)'%sys.argv[2]) os.write(1,'MITO000\n'.encode()) + # size of the compressed core source to be read + n=int(sys.argv[3]) # Read `len(compressed preamble)` bytes sent by our Mitogen parent. # `select()` handles non-blocking stdin (e.g. sudo + log_output). # `C` accumulates compressed bytes. C=''.encode() - while int(sys.argv[3])-len(C)and select.select([0],[],[]):C+=os.read(0,int(sys.argv[3])-len(C)) + # data chunk + V='V' + # Stop looping if no more data is needed or EOF is detected (empty bytes). + while n-len(C) and V:select.select([0],[],[]);V=os.read(0,n-len(C));C+=V # Raises `zlib.error` if compressed preamble is truncated or invalid C=zlib.decompress(C) f=os.fdopen(W,'wb',0) @@ -1450,6 +1455,10 @@ class Connection(object): f.write(C) f.close() os.write(1,'MITO001\n'.encode()) + # Final os.close(STDERR_FILENO) to avoid `--py-debug` build corrupting + # stream with "[1234 refs]" during exit. + # If STDERR is already closed an OSError is raised, but no one cares + # as STDERR is closed and the exit status is not forwarded. os.close(2) def get_python_argv(self): diff --git a/tests/first_stage_test.py b/tests/first_stage_test.py index 2576ec14..d901ef5c 100644 --- a/tests/first_stage_test.py +++ b/tests/first_stage_test.py @@ -50,3 +50,39 @@ class CommandLineTest(testlib.RouterMixin, testlib.TestCase): ) finally: fp.close() + + def test_premature_eof(self): + options = mitogen.parent.Options(max_message_size=123) + conn = mitogen.parent.Connection(options, self.router) + conn.context = mitogen.core.Context(None, 123) + + proc = testlib.subprocess.Popen( + args=conn.get_boot_command(), + stdout=testlib.subprocess.PIPE, + stderr=testlib.subprocess.PIPE, + stdin=testlib.subprocess.PIPE, + ) + + # Do not send all of the data from the preamble + proc.stdin.write(conn.get_preamble()[:-128]) + proc.stdin.flush() # XXX Is this redundant? Does close() alwys flush()? + proc.stdin.close() + try: + returncode = proc.wait(timeout=10) + except testlib.subprocess.TimeoutExpired: + proc.kill() + proc.wait(timeout=3) + self.fail("First stage did not handle EOF on STDIN") + try: + self.assertEqual(0, returncode) + self.assertEqual( + proc.stdout.read(), + mitogen.parent.BootstrapProtocol.EC0_MARKER + b("\n"), + ) + self.assertIn( + b("Error -5 while decompressing data"), + proc.stderr.read(), + ) + finally: + proc.stdout.close() + proc.stderr.close()