pr 超时:git/gh 子进程默认 120 秒超时,超时转 502 不挂 handler
此前 /pr 的子进程调用没有超时:远端通路卡死时 handler 线程永久挂住,客户端断开也收不回来。_run 真实路径一律 带 timeout(默认 120 秒,CLOUD_PR_SUBPROCESS_TIMEOUT 可改,非法值回落默认),超时杀子进程、返回 124,调用方 按原有失败路径处理(push/pr_create/commit 抛 PrStepError, 探针按跳过处理)。App 路径的 GitHub API 调用已有 30 秒 超时,本次不动。测试:真实 sleep 超时 2 个、环境变量 2 项、超时映射 push/commit 2 个
This commit is contained in:
1 parent
37d049d6b7
commit
f73c3f7a05
3 files changed
+188
-5
No files matched your search
+50
-4
@@ -47,6 +47,19 @@ host/<host 任务名>,worktree 在 <仓库根>/work/host-worktrees/
|
||||
(先读仓库 default_branch 再建,base 不猜 main)。没传
|
||||
app_token 时走原来的 gh 路径,两条路不互相干扰;App 没装
|
||||
在该仓库时调用方不传 token、自动回落 PAT。
|
||||
|
||||
超时:本模块所有 git/gh 子进程都有超时(默认 120 秒,
|
||||
环境变量 CLOUD_PR_SUBPROCESS_TIMEOUT 可改,非法值回落
|
||||
默认)。此前没有超时:远端通路卡死时(主机到 GitHub 的
|
||||
间歇性劣化曾让连接挂 120 秒以上)handler 线程会永久挂住,
|
||||
客户端断开也收不回来。超时只保证 handler 线程在时限内
|
||||
结束并返回 502(任务保持 merged、可安全重调),不能把
|
||||
结果送给已经断开的客户端;超时发生在 push 时远端引用
|
||||
未必已更新(git push 按引用原子更新,传一半被杀不会
|
||||
留下半个分支),重调安全。超时落在就绪探针(remote
|
||||
查询、gh auth)上时按原有跳过 reason 返回(no_remote /
|
||||
gh_not_authenticated),不单独区分——本地 git 命令正常
|
||||
在 1 秒内完成,探针超时只意味着 git 本身已 wedged。
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -60,6 +73,26 @@ _SKIP_NO_REMOTE = "no_remote"
|
||||
_SKIP_NO_GH = "no_gh"
|
||||
_SKIP_NOT_AUTHED = "gh_not_authenticated"
|
||||
|
||||
# 子进程超时(秒):远端通路卡死时 handler 线程的上限。
|
||||
# 正常 git 本地操作 < 1 秒、健康远端 push 几秒,120 秒只在
|
||||
# 通路劣化时才触发(此前实测主机到 GitHub 的挂起超过 120 秒)。
|
||||
_DEFAULT_SUBPROCESS_TIMEOUT = 120
|
||||
|
||||
|
||||
def _pr_subprocess_timeout():
|
||||
"""超时取值:环境变量 CLOUD_PR_SUBPROCESS_TIMEOUT(整数秒),
|
||||
未设、非整数或 <= 0 时回落默认。"""
|
||||
raw = os.environ.get("CLOUD_PR_SUBPROCESS_TIMEOUT")
|
||||
if raw is None:
|
||||
return _DEFAULT_SUBPROCESS_TIMEOUT
|
||||
try:
|
||||
value = int(raw)
|
||||
except (TypeError, ValueError):
|
||||
return _DEFAULT_SUBPROCESS_TIMEOUT
|
||||
if value <= 0:
|
||||
return _DEFAULT_SUBPROCESS_TIMEOUT
|
||||
return value
|
||||
|
||||
|
||||
class PrSkipped(Exception):
|
||||
"""跳过:reason 见上列常量,调用方返回 200 + pr_skipped。"""
|
||||
@@ -105,19 +138,32 @@ def _scrub_url(text):
|
||||
return re.sub(r"(https?://)[^/@\s]+@", r"\1", text)
|
||||
|
||||
|
||||
def _run(cmd, cwd=None, runner=None, env=None):
|
||||
def _run(cmd, cwd=None, runner=None, env=None, timeout=None):
|
||||
"""命令唯一入口:runner 供单测注入,返回带 returncode/stdout/
|
||||
stderr 的对象;真实路径走 subprocess。env 只在真实路径生效
|
||||
(与进程环境合并),注入的 runner 不收 env——token 绝不进
|
||||
假 runner 记录的命令,也不进真实路径的命令行参数。"""
|
||||
假 runner 记录的命令,也不进真实路径的命令行参数。
|
||||
超时:真实路径一律带 timeout(默认见 _pr_subprocess_timeout),
|
||||
超时杀掉子进程并返回 returncode=124 的结果,调用方按原有
|
||||
失败路径处理(push/pr_create/commit 抛 PrStepError,探针
|
||||
按跳过处理);注入的 runner 不收 timeout,单测用返回值
|
||||
模拟超时映射,不模拟真实挂起。"""
|
||||
if runner is not None:
|
||||
return runner(cmd, cwd)
|
||||
if timeout is None:
|
||||
timeout = _pr_subprocess_timeout()
|
||||
full_env = None
|
||||
if env:
|
||||
full_env = dict(os.environ)
|
||||
full_env.update(env)
|
||||
return subprocess.run(cmd, cwd=cwd, capture_output=True, text=True,
|
||||
env=full_env)
|
||||
try:
|
||||
return subprocess.run(cmd, cwd=cwd, capture_output=True, text=True,
|
||||
env=full_env, timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
return subprocess.CompletedProcess(
|
||||
cmd, 124, "",
|
||||
"command timed out after %s seconds: %s"
|
||||
% (timeout, " ".join(cmd)))
|
||||
|
||||
|
||||
_GITHUB_REMOTE_RE = re.compile(
|
||||
|
||||
Reference in new issue
Block a user