fix(exec): avoid flaky async subprocess timeouts

This commit is contained in:
Hua
2026-03-15 19:13:22 +08:00
parent f1ed17051f
commit 0f5db9a7ff

View File

@@ -3,6 +3,8 @@
import asyncio import asyncio
import os import os
import re import re
import subprocess
import tempfile
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -91,26 +93,31 @@ class ExecTool(Tool):
env["PATH"] = env.get("PATH", "") + os.pathsep + self.path_append env["PATH"] = env.get("PATH", "") + os.pathsep + self.path_append
try: try:
process = await asyncio.create_subprocess_shell( with tempfile.TemporaryFile() as stdout_file, tempfile.TemporaryFile() as stderr_file:
command, process = subprocess.Popen(
stdout=asyncio.subprocess.PIPE, command,
stderr=asyncio.subprocess.PIPE, stdout=stdout_file,
cwd=cwd, stderr=stderr_file,
env=env, cwd=cwd,
) env=env,
shell=True,
try:
stdout, stderr = await asyncio.wait_for(
process.communicate(),
timeout=effective_timeout,
) )
except asyncio.TimeoutError:
process.kill() deadline = asyncio.get_running_loop().time() + effective_timeout
try: while process.poll() is None:
await asyncio.wait_for(process.wait(), timeout=5.0) if asyncio.get_running_loop().time() >= deadline:
except asyncio.TimeoutError: process.kill()
pass try:
return f"Error: Command timed out after {effective_timeout} seconds" process.wait(timeout=5.0)
except subprocess.TimeoutExpired:
pass
return f"Error: Command timed out after {effective_timeout} seconds"
await asyncio.sleep(0.05)
stdout_file.seek(0)
stderr_file.seek(0)
stdout = stdout_file.read()
stderr = stderr_file.read()
output_parts = [] output_parts = []