fix(auto-deploy): address PR #44 review feedback (M1, M2)
CI / lint (pull_request) Successful in 6s
CI / test (pull_request) Successful in 8s
CI / notify-on-failure (pull_request) Successful in 0s

M1: Replace nohup with asyncio.sleep - preserves subprocess output/error detection
M2: Use PM2_HOME env check + regex matching instead of fragile string match
S1: pm2_name now has clear purpose for M2's regex-based self-restart detection

405 passed, 3 skipped.
This commit is contained in:
cfdaily
2026-06-12 15:07:43 +08:00
parent d82d29fd79
commit 33c58a7dae
+26 -8
View File
@@ -561,17 +561,35 @@ async def _handle_pr_closed(payload: Dict[str, Any]) -> None:
for cmd in post_deploy_cmds:
logger.info("Auto-deploy: executing post_deploy: %s", cmd)
# 如果命令会 restart 当前进程(自己杀自己),用延迟执行
# 避免当前 webhook handler 被杀导致部署失败
if pm2_name and f"pm2 restart {pm2_name}" in cmd:
actual_cmd = f"nohup sh -c 'sleep 2 && {cmd}' > /dev/null 2>&1 &"
# M2: 检测当前进程是否会被此命令杀掉(而非脆弱的字符串匹配)
# 通过 PM2 环境变量判断:pm2 启动的进程有 PM2_HOME
self_restart = False
if pm2_name and os.environ.get("PM2_HOME") and "pm2 restart" in cmd:
# 检查命令是否包含当前进程名
import re
if re.search(rf'pm2\s+restart\s+{re.escape(pm2_name)}', cmd):
self_restart = True
if self_restart:
# M1: 用 asyncio.sleep 延迟而非 nohup,保留子进程输出和错误检测
# 先 sleep 让 handler 正常返回,再启动 restart 命令
# restart 的子进程会在父进程死后被 pm2 新进程接管
logger.info("Auto-deploy: self-restart detected, deferring 2s: %s", cmd)
await asyncio.sleep(2)
deploy_proc = await asyncio.create_subprocess_exec(
"sh", "-c", actual_cmd,
"sh", "-c", cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
# 不等待:nohup 后台执行,立即返回
logger.info("Auto-deploy: self-restart deferred (nohup): %s", cmd)
# restart 会杀掉当前进程,communicate 可能不会完成
# 但我们至少尝试读取输出
try:
_, deploy_err = await asyncio.wait_for(
deploy_proc.communicate(), timeout=10)
except (asyncio.TimeoutError, ProcessLookupError):
# 预期行为:进程被 pm2 restart 杀掉
logger.info("Auto-deploy: process killed by self-restart (expected)")
break
else:
deploy_proc = await asyncio.create_subprocess_exec(
"sh", "-c", cmd,
@@ -585,7 +603,7 @@ async def _handle_pr_closed(payload: Dict[str, Any]) -> None:
_send_deploy_failure_mail(repo, pr_number, pr_title, f"post_deploy 失败 ({cmd}): {deploy_err.decode()}")
break
else:
logger.info("Auto-deploy: all post_deploy commands scheduled/succeeded (files: %s)", ", ".join(file_list[:5]))
logger.info("Auto-deploy: all post_deploy commands succeeded (files: %s)", ", ".join(file_list[:5]))
else:
logger.info("Auto-deploy: docs-only change for %s, skip post_deploy", repo)