Skip to content

Commit b4a2bf9

Browse files
Add Windows process termination debug scripts
These scripts help investigate why process tree termination is failing on Windows for the three failing tests: - test_stdio_client_child_process_cleanup - test_stdio_client_early_parent_exit - test_stdio_client_nested_process_tree The scripts test different scenarios: 1. debug_process_tree.py - Tests taskkill /T and our recursive WMI approach 2. debug_process_flags.py - Tests different process creation flags 3. debug_process_hierarchy.py - Shows process parent-child relationships
1 parent d6d2cbd commit b4a2bf9

File tree

3 files changed

+351
-0
lines changed

3 files changed

+351
-0
lines changed

debug_process_flags.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""Debug script to test Windows process creation flags"""
2+
import subprocess
3+
import sys
4+
import time
5+
import tempfile
6+
import os
7+
8+
def test_process_flags():
9+
# Create a marker file
10+
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
11+
marker_file = f.name
12+
13+
print(f"Marker file: {marker_file}")
14+
15+
# Simple child script
16+
child_script = f'''
17+
import time
18+
with open({repr(marker_file)}, 'a') as f:
19+
while True:
20+
f.write(f"{{time.time()}} ")
21+
f.flush()
22+
time.sleep(0.1)
23+
'''
24+
25+
# Test 1: Default creation (inherits console)
26+
print("\n=== Test 1: Default process creation ===")
27+
proc1 = subprocess.Popen([sys.executable, "-c", child_script])
28+
print(f"Started process with PID: {proc1.pid}")
29+
time.sleep(0.5)
30+
31+
# Try taskkill /T
32+
result = subprocess.run(
33+
["taskkill", "/F", "/T", "/PID", str(proc1.pid)],
34+
capture_output=True,
35+
text=True
36+
)
37+
print(f"taskkill result: {result.returncode}")
38+
print(f"Output: {result.stdout}")
39+
40+
# Test 2: With CREATE_NEW_PROCESS_GROUP
41+
print("\n=== Test 2: With CREATE_NEW_PROCESS_GROUP ===")
42+
proc2 = subprocess.Popen(
43+
[sys.executable, "-c", child_script],
44+
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
45+
)
46+
print(f"Started process with PID: {proc2.pid}")
47+
time.sleep(0.5)
48+
49+
# Try taskkill /T
50+
result = subprocess.run(
51+
["taskkill", "/F", "/T", "/PID", str(proc2.pid)],
52+
capture_output=True,
53+
text=True
54+
)
55+
print(f"taskkill result: {result.returncode}")
56+
print(f"Output: {result.stdout}")
57+
58+
# Test 3: With CREATE_NO_WINDOW
59+
print("\n=== Test 3: With CREATE_NO_WINDOW ===")
60+
proc3 = subprocess.Popen(
61+
[sys.executable, "-c", child_script],
62+
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0)
63+
)
64+
print(f"Started process with PID: {proc3.pid}")
65+
time.sleep(0.5)
66+
67+
# Try taskkill /T
68+
result = subprocess.run(
69+
["taskkill", "/F", "/T", "/PID", str(proc3.pid)],
70+
capture_output=True,
71+
text=True
72+
)
73+
print(f"taskkill result: {result.returncode}")
74+
print(f"Output: {result.stdout}")
75+
76+
# Clean up
77+
try:
78+
os.unlink(marker_file)
79+
except:
80+
pass
81+
82+
if __name__ == "__main__":
83+
test_process_flags()

debug_process_hierarchy.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""Debug script to understand Windows process hierarchy"""
2+
import subprocess
3+
import sys
4+
import time
5+
import os
6+
7+
def show_process_tree():
8+
"""Show the full process tree for Python processes"""
9+
print("\n=== Full Python Process Tree ===")
10+
11+
# Get all python processes
12+
result = subprocess.run(
13+
["wmic", "process", "where", "Name='python.exe'", "get", "ProcessId,ParentProcessId,CommandLine", "/FORMAT:LIST"],
14+
capture_output=True,
15+
text=True
16+
)
17+
18+
processes = []
19+
current = {}
20+
21+
for line in result.stdout.strip().split('\n'):
22+
line = line.strip()
23+
if not line:
24+
if current and 'ProcessId' in current:
25+
processes.append(current)
26+
current = {}
27+
elif '=' in line:
28+
key, value = line.split('=', 1)
29+
current[key] = value
30+
31+
if current and 'ProcessId' in current:
32+
processes.append(current)
33+
34+
# Build tree
35+
print("\nProcess Hierarchy:")
36+
for proc in processes:
37+
pid = proc.get('ProcessId', '')
38+
ppid = proc.get('ParentProcessId', '')
39+
cmd = proc.get('CommandLine', '')[:100] + '...' if len(proc.get('CommandLine', '')) > 100 else proc.get('CommandLine', '')
40+
print(f"PID: {pid} <- Parent: {ppid}")
41+
print(f" Cmd: {cmd}")
42+
print()
43+
44+
def test_nested_hierarchy():
45+
"""Create a nested process hierarchy and show relationships"""
46+
47+
# Grandchild script
48+
grandchild_script = '''
49+
import os
50+
import time
51+
print(f"Grandchild PID: {os.getpid()}")
52+
while True:
53+
time.sleep(0.1)
54+
'''
55+
56+
# Child script that spawns grandchild
57+
child_script = f'''
58+
import subprocess
59+
import sys
60+
import os
61+
import time
62+
63+
print(f"Child PID: {{os.getpid()}}")
64+
65+
# Spawn grandchild
66+
grandchild = subprocess.Popen([sys.executable, '-c', {repr(grandchild_script)}])
67+
68+
while True:
69+
time.sleep(0.1)
70+
'''
71+
72+
# Parent script that spawns child
73+
parent_script = f'''
74+
import subprocess
75+
import sys
76+
import os
77+
import time
78+
79+
print(f"Parent PID: {{os.getpid()}}")
80+
81+
# Spawn child
82+
child = subprocess.Popen([sys.executable, '-c', {repr(child_script)}])
83+
84+
while True:
85+
time.sleep(0.1)
86+
'''
87+
88+
print("Creating nested process hierarchy...")
89+
90+
# Start parent
91+
parent = subprocess.Popen([sys.executable, "-c", parent_script])
92+
print(f"Top-level parent PID: {parent.pid}")
93+
94+
# Wait for all processes to start
95+
time.sleep(1.0)
96+
97+
# Show the tree
98+
show_process_tree()
99+
100+
# Test taskkill on parent
101+
print("\n=== Testing taskkill /T on parent ===")
102+
result = subprocess.run(
103+
["taskkill", "/F", "/T", "/PID", str(parent.pid)],
104+
capture_output=True,
105+
text=True
106+
)
107+
print(f"Result: {result.returncode}")
108+
print(f"Output: {result.stdout}")
109+
print(f"Error: {result.stderr}")
110+
111+
# Show tree after
112+
time.sleep(0.5)
113+
print("\n=== Process tree after taskkill ===")
114+
show_process_tree()
115+
116+
if __name__ == "__main__":
117+
test_nested_hierarchy()

debug_process_tree.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""Debug script to test Windows process tree termination"""
2+
import subprocess
3+
import sys
4+
import time
5+
import tempfile
6+
import os
7+
import asyncio
8+
9+
async def test_recursive_kill(pid):
10+
"""Test our recursive kill implementation"""
11+
print(f"\n=== Testing recursive kill for PID {pid} ===")
12+
13+
# First, show process tree before
14+
print("\nProcess tree before kill:")
15+
wmic_result = subprocess.run(
16+
["wmic", "process", "where", f"ParentProcessId={pid} or ProcessId={pid}", "get", "ProcessId,ParentProcessId,Name"],
17+
capture_output=True,
18+
text=True
19+
)
20+
print(wmic_result.stdout)
21+
22+
# Import and test our function
23+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
24+
from mcp.client.stdio import _kill_process_tree_windows
25+
26+
# Run the kill function
27+
await _kill_process_tree_windows(pid)
28+
29+
# Show process tree after
30+
time.sleep(0.5)
31+
print("\nProcess tree after kill:")
32+
wmic_result = subprocess.run(
33+
["wmic", "process", "where", f"ParentProcessId={pid} or ProcessId={pid}", "get", "ProcessId,ParentProcessId,Name"],
34+
capture_output=True,
35+
text=True
36+
)
37+
print(wmic_result.stdout)
38+
39+
def test_process_tree():
40+
# Create a marker file
41+
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
42+
marker_file = f.name
43+
44+
print(f"Marker file: {marker_file}")
45+
46+
# Parent script that spawns a child
47+
parent_script = f'''
48+
import subprocess
49+
import sys
50+
import time
51+
import os
52+
53+
# Child script that writes continuously
54+
child_script = """
55+
import time
56+
with open({repr(marker_file)}, 'a') as f:
57+
while True:
58+
f.write(f"{{time.time()}}")
59+
f.flush()
60+
time.sleep(0.1)
61+
"""
62+
63+
# Start the child process
64+
print(f"Parent PID: {{os.getpid()}}")
65+
child = subprocess.Popen([sys.executable, '-c', child_script])
66+
print(f"Child PID: {{child.pid}}")
67+
68+
# Parent just sleeps
69+
while True:
70+
time.sleep(0.1)
71+
'''
72+
73+
# Start parent process
74+
print("Starting parent process...")
75+
parent = subprocess.Popen([sys.executable, "-c", parent_script])
76+
print(f"Parent started with PID: {parent.pid}")
77+
78+
# Wait for child to start writing
79+
time.sleep(1.0)
80+
81+
# Check if file is being written
82+
if os.path.exists(marker_file):
83+
size1 = os.path.getsize(marker_file)
84+
time.sleep(0.5)
85+
size2 = os.path.getsize(marker_file)
86+
print(f"File is growing: {size1} -> {size2} bytes")
87+
88+
# Show initial process tree
89+
print("\n=== Initial process tree ===")
90+
wmic_result = subprocess.run(
91+
["wmic", "process", "where", f"ProcessId={parent.pid}", "get", "ProcessId,ParentProcessId,Name", "/FORMAT:VALUE"],
92+
capture_output=True,
93+
text=True
94+
)
95+
print("Parent process info:")
96+
print(wmic_result.stdout)
97+
98+
# Try different termination methods
99+
print("\n=== Method 1: taskkill /T ===")
100+
result = subprocess.run(
101+
["taskkill", "/F", "/T", "/PID", str(parent.pid)],
102+
capture_output=True,
103+
text=True
104+
)
105+
print(f"Return code: {result.returncode}")
106+
print(f"Stdout: {result.stdout}")
107+
print(f"Stderr: {result.stderr}")
108+
109+
# Check if processes stopped
110+
time.sleep(0.5)
111+
if os.path.exists(marker_file):
112+
size3 = os.path.getsize(marker_file)
113+
time.sleep(0.5)
114+
size4 = os.path.getsize(marker_file)
115+
if size4 > size3:
116+
print(f"FAILED: File still growing: {size3} -> {size4} bytes")
117+
118+
# Try to find child processes
119+
print("\n=== Finding child processes with wmic ===")
120+
wmic_result = subprocess.run(
121+
["wmic", "process", "where", f"ParentProcessId={parent.pid}", "get", "ProcessId,ParentProcessId,Name", "/FORMAT:VALUE"],
122+
capture_output=True,
123+
text=True
124+
)
125+
print("Child processes still running:")
126+
print(wmic_result.stdout)
127+
128+
# Test our recursive approach
129+
print("\n=== Testing recursive kill approach ===")
130+
asyncio.run(test_recursive_kill(parent.pid))
131+
132+
# Final check
133+
time.sleep(0.5)
134+
size5 = os.path.getsize(marker_file)
135+
time.sleep(0.5)
136+
size6 = os.path.getsize(marker_file)
137+
if size6 > size5:
138+
print(f"STILL FAILED: File still growing after recursive kill: {size5} -> {size6} bytes")
139+
else:
140+
print(f"SUCCESS with recursive kill: File stopped growing at {size6} bytes")
141+
else:
142+
print(f"SUCCESS: File stopped growing at {size4} bytes")
143+
144+
# Clean up
145+
try:
146+
os.unlink(marker_file)
147+
except:
148+
pass
149+
150+
if __name__ == "__main__":
151+
test_process_tree()

0 commit comments

Comments
 (0)