Skip to content

Commit f9603a9

Browse files
authored
Merge pull request #2366 from willend/main
mctest: Even more elaborate process-control / termination logic
2 parents 5eeb69a + 3a625b0 commit f9603a9

1 file changed

Lines changed: 94 additions & 13 deletions

File tree

tools/Python/mccodelib/utils.py

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import re
55
import os
66
import sys
7+
import time
8+
import signal
79
from os.path import splitext, join
810
import subprocess
911
from datetime import datetime
@@ -773,7 +775,61 @@ def get_file_contents(filepath):
773775
else:
774776
return ''
775777

776-
def run_subtool_noread(cmd, cwd=None, timeout=None):
778+
try:
779+
import psutil
780+
except ImportError:
781+
psutil = None
782+
783+
def _kill_process_tree(pid, timeout=3.0):
784+
"""Kill process tree rooted at pid. Uses psutil if available, otherwise best-effort."""
785+
if psutil:
786+
try:
787+
parent = psutil.Process(pid)
788+
except psutil.NoSuchProcess:
789+
return
790+
children = parent.children(recursive=True)
791+
# send sig to children first
792+
for p in children:
793+
try:
794+
p.send_signal(signal.SIGKILL)
795+
except Exception:
796+
try:
797+
p.kill()
798+
except Exception:
799+
pass
800+
# then parent
801+
try:
802+
parent.send_signal(sig)
803+
except Exception:
804+
try:
805+
parent.kill()
806+
except Exception:
807+
pass
808+
# wait up to timeout for processes to disappear
809+
gone, alive = psutil.wait_procs([parent] + children, timeout=timeout)
810+
return alive
811+
else:
812+
# Fallback: on Unix killpg, on Windows try proc.kill
813+
if sys.platform == "win32":
814+
try:
815+
os.kill(pid, signal.SIGTERM)
816+
except Exception:
817+
try:
818+
os.kill(pid, signal.SIGKILL)
819+
except Exception:
820+
pass
821+
else:
822+
try:
823+
os.killpg(os.getpgid(pid), signal.SIGTERM)
824+
except Exception:
825+
try:
826+
os.kill(pid, signal.SIGTERM)
827+
except Exception:
828+
pass
829+
# no reliable wait for children without psutil
830+
return []
831+
832+
def run_subtool_noread(cmd, cwd=None, timeout=None, kill_timeout=3.0):
777833
"""Run external command without reading output; kill whole process group on timeout.
778834
Returns (returncode, timed_out: bool).
779835
"""
@@ -790,7 +846,7 @@ def run_subtool_noread(cmd, cwd=None, timeout=None):
790846
preexec_fn = os.setpgrp # start new session -> new process group
791847

792848
try:
793-
process = subprocess.Popen(
849+
proc = subprocess.Popen(
794850
cmd,
795851
stdout=subprocess.PIPE,
796852
stderr=subprocess.PIPE,
@@ -803,25 +859,50 @@ def run_subtool_noread(cmd, cwd=None, timeout=None):
803859
)
804860

805861
try:
806-
process.communicate(timeout=timeout)
807-
return process.returncode, False
862+
proc.communicate(timeout=timeout)
863+
return proc.returncode, False
808864
except subprocess.TimeoutExpired:
809-
# Kill the whole process group
865+
# escalate: try gentle signal first
810866
try:
811867
if sys.platform == "win32":
812-
# send CTRL_BREAK_EVENT to the process group
813-
process.send_signal(signal.CTRL_BREAK_EVENT)
868+
try:
869+
proc.send_signal(signal.CTRL_BREAK_EVENT)
870+
except Exception:
871+
proc.terminate()
814872
else:
815-
os.killpg(os.getpgid(process.pid), signal.SIGKILL)
873+
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
874+
except Exception:
875+
try:
876+
proc.terminate()
877+
except Exception:
878+
pass
879+
880+
# wait a short time
881+
try:
882+
proc.wait(timeout=kill_timeout)
816883
except Exception:
817-
# fallback to killing the process
884+
# still alive -> force kill whole tree
885+
_kill_process_tree(proc.pid, sig=signal.SIGKILL, timeout=kill_timeout)
818886
try:
819-
process.kill()
887+
proc.wait(timeout=kill_timeout)
820888
except Exception:
821889
pass
822-
# Wait for termination
823-
process.wait()
824-
return process.returncode if process.returncode is not None else -1, True
890+
891+
return (proc.returncode if proc.returncode is not None else -1), True
892+
finally:
893+
# close fds
894+
try:
895+
proc.stdout.close()
896+
except Exception:
897+
pass
898+
try:
899+
proc.stderr.close()
900+
except Exception:
901+
pass
902+
try:
903+
proc.stdin.close()
904+
except Exception:
905+
pass
825906

826907
except Exception as e:
827908
# unicode/read error safe-guard

0 commit comments

Comments
 (0)