1+ # !/usr/bin/env pwsh
2+ # Script to stress test the specific race condition in stdio cleanup
3+ # This creates many processes rapidly to expose handle/job object races
4+ # Usage: .\test-stdio-stress-race.ps1
5+ #
6+ # Prerequisites: Run . .\setup-environment.ps1 first to ensure tee is available
7+
8+ Write-Host " Stress testing stdio cleanup race conditions..." - ForegroundColor Cyan
9+ Write-Host " This test creates many processes rapidly to expose timing issues." - ForegroundColor Yellow
10+ Write-Host " "
11+
12+ # Check if tee is available
13+ $teeCheck = python - c " import shutil; print(shutil.which('tee'))"
14+ if (-not $teeCheck -or $teeCheck -eq " None" ) {
15+ Write-Host " ERROR: tee command not found!" - ForegroundColor Red
16+ Write-Host " Please run: . .\setup-environment.ps1" - ForegroundColor Yellow
17+ Write-Host " (Note the dot at the beginning to source the script)" - ForegroundColor Yellow
18+ exit 1
19+ }
20+
21+ # Create a Python script that runs the test many times in quick succession
22+ $stressScript = @'
23+ import asyncio
24+ import sys
25+ import time
26+ from pathlib import Path
27+
28+ # Add parent directories to path
29+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
30+
31+ from mcp.client.stdio import stdio_client, StdioServerParameters
32+
33+ async def rapid_test(test_id: int):
34+ """Run a single test iteration"""
35+ try:
36+ async with stdio_client(StdioServerParameters(command="tee")) as (_, _):
37+ pass
38+ return True, None
39+ except Exception as e:
40+ return False, str(e)
41+
42+ async def stress_test(iterations: int, concurrent: int):
43+ """Run many tests concurrently"""
44+ print(f"Running {iterations} tests with {concurrent} concurrent...")
45+
46+ failures = 0
47+ errors = []
48+ start_time = time.time()
49+
50+ # Run in batches
51+ for batch in range(0, iterations, concurrent):
52+ batch_size = min(concurrent, iterations - batch)
53+ tasks = [rapid_test(batch + i) for i in range(batch_size)]
54+ results = await asyncio.gather(*tasks)
55+
56+ for success, error in results:
57+ if not success:
58+ failures += 1
59+ if error and error not in errors:
60+ errors.append(error)
61+
62+ # Progress indicator
63+ if (batch + batch_size) % 100 == 0:
64+ print(f" Completed {batch + batch_size}/{iterations} tests...")
65+
66+ duration = time.time() - start_time
67+ return failures, errors, duration
68+
69+ async def main():
70+ # Test different concurrency levels
71+ configs = [
72+ (100, 1), # Sequential
73+ (100, 2), # Low concurrency
74+ (100, 5), # Medium concurrency
75+ (100, 10), # High concurrency
76+ ]
77+
78+ for iterations, concurrent in configs:
79+ print(f"\nTest: {iterations} iterations, {concurrent} concurrent")
80+ failures, errors, duration = await stress_test(iterations, concurrent)
81+
82+ print(f" Duration: {duration:.2f}s")
83+ print(f" Failures: {failures}/{iterations}")
84+ if errors:
85+ print(f" Unique errors: {len(errors)}")
86+ for error in errors[:3]: # Show first 3 errors
87+ print(f" - {error}")
88+
89+ if failures > 0:
90+ print(" RACE CONDITION DETECTED!" if concurrent > 1 else " FAILURE DETECTED!")
91+
92+ if __name__ == "__main__":
93+ asyncio.run(main())
94+ '@
95+
96+ # Save the stress test script
97+ $scriptPath = Join-Path $PSScriptRoot " stress_test.py"
98+ $stressScript | Out-File - FilePath $scriptPath - Encoding UTF8
99+
100+ Write-Host " Running stress tests..." - ForegroundColor Cyan
101+ Write-Host " "
102+
103+ # Run the stress test
104+ uv run python $scriptPath
105+
106+ $exitCode = $LASTEXITCODE
107+
108+ # Clean up
109+ Remove-Item $scriptPath - ErrorAction SilentlyContinue
110+
111+ Write-Host " "
112+ Write-Host " ========== ANALYSIS ==========" - ForegroundColor Cyan
113+
114+ if ($exitCode -ne 0 ) {
115+ Write-Host " Stress test failed to complete." - ForegroundColor Red
116+ } else {
117+ Write-Host " Stress test completed." - ForegroundColor Green
118+ Write-Host " "
119+ Write-Host " If failures increased with concurrency, it indicates:" - ForegroundColor Yellow
120+ Write-Host " - Race condition in process cleanup" - ForegroundColor Gray
121+ Write-Host " - Job Object handle conflicts" - ForegroundColor Gray
122+ Write-Host " - Windows handle inheritance issues" - ForegroundColor Gray
123+ Write-Host " "
124+ Write-Host " This matches the CI flakiness pattern where parallel tests fail." - ForegroundColor Yellow
125+ }
0 commit comments