From 82a0efcb31b00178b3c7321ac3f6eb3ac73c6ce4 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Thu, 30 Oct 2025 15:56:10 +0000 Subject: [PATCH 1/2] mitogen: Allow line comments in first stage, strip them. --- mitogen/parent.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mitogen/parent.py b/mitogen/parent.py index 6e30b1c6..71641d36 100644 --- a/mitogen/parent.py +++ b/mitogen/parent.py @@ -1463,8 +1463,9 @@ class Connection(object): return [self.options.python_path] def get_boot_command(self): - source = inspect.getsource(self._first_stage) - source = textwrap.dedent('\n'.join(source.strip().split('\n')[2:])) + lines = inspect.getsourcelines(self._first_stage)[0][2:] + # Remove line comments, leading indentation, trailing newline + source = textwrap.dedent(''.join(s for s in lines if '#' not in s))[:-1] source = source.replace(' ', ' ') compressor = zlib.compressobj( zlib.Z_BEST_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS, From 03a0a151ff004959abbdff0b15d21cc3266dd960 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Thu, 27 Nov 2025 10:02:14 +0000 Subject: [PATCH 2/2] mitogen: Kill (hung) bootstrap processes after 5 second timeout Since using select.select() in the first stage (to handle an obscure corner case where stdin appears to be non-blocking) there has been a report of first stage processes running for ever in an infinite loop - reading 0 bytes from stdin. This attempts to do an end run around that problem by aborting if the bootstrap takes longer than a few seconds for *any* reason. Existing retry logic should deal with it as before. 5 seconds is a best guess at a suitable timeout. --- docs/changelog.rst | 3 +++ mitogen/parent.py | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 3fd0ab54..6c078e56 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -21,6 +21,9 @@ To avail of fixes in an unreleased version, please download a ZIP file In progress (unreleased) ------------------------ +* :gh:issue:`1266` :mod:`mitogen`: Prevent hung bootstrap processes, add 5 + second timeout to first stage + v0.3.34 (2025-11-27) -------------------- diff --git a/mitogen/parent.py b/mitogen/parent.py index 71641d36..3dee2cb4 100644 --- a/mitogen/parent.py +++ b/mitogen/parent.py @@ -1436,6 +1436,10 @@ class Connection(object): if os.uname()[0]+os.uname()[2][:2]+sys.version[:3]=='Darwin212.7':os.environ['PYTHON_LAUNCHED_FROM_WRAPPER']='1' os.environ['ARGV0']=sys.executable os.execl(sys.executable,sys.executable+'(mitogen:%s)'%sys.argv[2]) + # Timeout execution after a few seconds, to prevent hung processes. + # Cause might be closed/reset pipe/stream backing stdin (fd=0); + # or blocking write to interpreter stdin (fd=W, fd=w). + signal.alarm(5) os.write(1,'MITO000\n'.encode()) C=''.encode() while int(sys.argv[3])-len(C)and select.select([0],[],[]):C+=os.read(0,int(sys.argv[3])-len(C)) @@ -1476,11 +1480,11 @@ class Connection(object): # Just enough to decode, decompress, and exec the first stage. # Priorities: wider compatibility, faster startup, shorter length. # `sys.path=...` for https://github.com/python/cpython/issues/115911. - # `import os,select` here (not stage 1) to save a few bytes overall. + # `import os,...` here (not stage 1) saves a few bytes overall. return self.get_python_argv() + [ '-c', 'import sys;sys.path=[p for p in sys.path if p];' - 'import binascii,os,select,zlib;' + 'import binascii,os,select,signal,zlib;' 'exec(zlib.decompress(binascii.a2b_base64(sys.argv[1]),-15))', encoded.decode(), self.options.remote_name,