From 5f028dae4c38b0c362da44428cf2a783cb8458a1 Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Fri, 7 Aug 2026 22:35:08 +0800 Subject: [PATCH] =?UTF-8?q?=E9=83=A8=E5=88=86=E9=80=BB=E8=BE=91=E4=BC=98?= =?UTF-8?q?=E5=8C=96=EF=BC=8C=E6=B6=89=E5=8F=8A=E8=BF=9B=E7=A8=8B=E6=A3=80?= =?UTF-8?q?=E6=B5=8B=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy.sh | 10 ++-- internal/process/manager.go | 101 +++++++++++++++++++++++++++++++----- 2 files changed, 92 insertions(+), 19 deletions(-) diff --git a/deploy.sh b/deploy.sh index 4839775..6b8f7b3 100644 --- a/deploy.sh +++ b/deploy.sh @@ -666,10 +666,10 @@ print_environment_summary() { print_title print_subtitle "环境检测结果" echo "" - echo -e " 操作系统:${NC} $OS $OS_VERSION" - echo -e " CPU 架构:${NC} $ARCH" + echo -e " 操作系统: $OS $OS_VERSION" + echo -e " CPU 架构: $ARCH" echo "" - echo " 必要工具:${NC}" + echo " 必要工具:" if [ "$HAS_GIT" = true ]; then echo -e " git ✅ 已安装 ($(git --version | awk '{print $3}'))" else @@ -686,7 +686,7 @@ print_environment_summary() { echo " wget ❌ 未安装 (将自动安装)" fi echo "" - echo " Docker 环境:${NC}" + echo " Docker 环境:" if [ "$HAS_DOCKER" = true ]; then echo -e " docker ✅ 已安装 ($(docker --version | awk '{print $3}' | tr -d ','))" else @@ -696,7 +696,7 @@ print_environment_summary() { fi if [ "$CONTAINER_EXISTS" = true ]; then echo "" - echo " 容器状态:${NC}" + echo " 容器状态:" if [ "$CONTAINER_RUNNING" = true ]; then echo -e " frpc-console ✅ 运行中" else diff --git a/internal/process/manager.go b/internal/process/manager.go index dbe7d3a..71bdaa7 100644 --- a/internal/process/manager.go +++ b/internal/process/manager.go @@ -17,6 +17,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strconv" "strings" "sync" @@ -31,11 +32,15 @@ import ( const ( LockFileName = ".frpc.lock" PortCheckTimeout = 500 * time.Millisecond - StartWaitTime = 500 * time.Millisecond StopMaxWaitTime = 5 * time.Second LockAcquireTimeout = 30 * time.Second LockRetryInterval = 100 * time.Millisecond APITimeout = 2 * time.Second + + // 启动检测参数 + StartupMaxAttempts = 25 // 最多检测 25 次 + StartupRetryDelay = 200 * time.Millisecond // 每次间隔 200ms + // 总超时 = 25 * 200ms = 5 秒 ) // ================================================================ @@ -115,7 +120,7 @@ func (pm *ProcessManager) AdminPort() int { } // ================================================================ -// 配置读取 (带调试日志) +// 配置读取 // ================================================================ func (pm *ProcessManager) LoadConfig() error { @@ -390,6 +395,32 @@ func (pm *ProcessManager) isFRPCProcess(pid int) (bool, string) { return false, "" } +// ================================================================ +// 进程存活检测 +// ================================================================ + +// isProcessAlive 检查 PID 对应的进程是否存活 +func (pm *ProcessManager) isProcessAlive(pid int) bool { + if pid <= 0 { + return false + } + + if runtime.GOOS == "windows" { + cmd := exec.Command("tasklist", "/FI", "PID eq", strconv.Itoa(pid)) + output, err := cmd.CombinedOutput() + if err != nil { + return false + } + return strings.Contains(string(output), strconv.Itoa(pid)) + } + + process, err := os.FindProcess(pid) + if err != nil { + return false + } + return process.Signal(syscall.Signal(0)) == nil +} + // ================================================================ // PID 文件操作 // ================================================================ @@ -525,19 +556,46 @@ func (pm *ProcessManager) startLocked(ctx context.Context) error { if err := cmd.Start(); err != nil { return fmt.Errorf("启动 frpc 失败: %w", err) } - time.Sleep(StartWaitTime) - occupied, err := pm.CheckPort() - if err != nil { - return fmt.Errorf("验证启动状态失败: %w", err) + + // ============================================================ + // 组合状态检测:最多等待 5 秒,三条件全部满足才算成功 + // ============================================================ + for attempt := 0; attempt < StartupMaxAttempts; attempt++ { + // 条件1: 检查 PID 文件 + pid := pm.readPIDFile() + if pid <= 0 { + time.Sleep(StartupRetryDelay) + continue + } + + // 条件2: 检查进程是否存活 + if !pm.isProcessAlive(pid) { + time.Sleep(StartupRetryDelay) + continue + } + + // 条件3: 检查端口是否可连接 + occupied, err := pm.CheckPort() + if err == nil && occupied { + // 三项全部满足! + pm.writePIDFile(pid) + log.Printf("[INFO] frpc 启动成功 (PID: %d, 端口: %d),耗时 %dms", + pid, pm.adminPort, attempt*int(StartupRetryDelay/time.Millisecond)) + return nil + } + + time.Sleep(StartupRetryDelay) } - if !occupied { - return fmt.Errorf("frpc 启动失败: 端口未监听") - } - pid, _ := pm.getPIDByPort(pm.adminPort) + + // 超时:输出诊断信息 + pid := pm.readPIDFile() if pid > 0 { - pm.writePIDFile(pid) + alive := pm.isProcessAlive(pid) + occupied, _ := pm.CheckPort() + return fmt.Errorf("frpc 启动超时: PID=%d, 进程存活=%v, 端口监听=%v", pid, alive, occupied) } - return nil + + return fmt.Errorf("frpc 启动超时: 未生成 PID 文件") } func (pm *ProcessManager) Stop(ctx context.Context) error { @@ -548,7 +606,7 @@ func (pm *ProcessManager) Stop(ctx context.Context) error { return pm.stopLocked(ctx) } -func (pm *ProcessManager) stopLocked(ctx context.Context) error { +func (pm *ProcessManager) stopLocked(_ context.Context) error { portStatus, err := pm.GetPortStatus() if err != nil { return fmt.Errorf("检测端口状态失败: %w", err) @@ -569,6 +627,8 @@ func (pm *ProcessManager) stopLocked(ctx context.Context) error { return fmt.Errorf("无法确定 frpc 进程 PID") } } + + // 发送 SIGTERM (优雅停止) proc, err := os.FindProcess(pid) if err != nil { pm.deletePIDFile() @@ -578,6 +638,8 @@ func (pm *ProcessManager) stopLocked(ctx context.Context) error { pm.deletePIDFile() return nil } + + // 等待端口释放 start := time.Now() for time.Since(start) < StopMaxWaitTime { occupied, _ := pm.CheckPort() @@ -587,11 +649,20 @@ func (pm *ProcessManager) stopLocked(ctx context.Context) error { } time.Sleep(200 * time.Millisecond) } - proc.Kill() + + // 端口未释放, 强制 kill + if runtime.GOOS == "windows" { + cmd := exec.Command("taskkill", "/F", "/IM", "frpc.exe") + cmd.Run() + } else { + proc.Kill() + } time.Sleep(500 * time.Millisecond) + if occupied, _ := pm.CheckPort(); occupied { return fmt.Errorf("强制停止失败: 端口仍被占用") } + pm.deletePIDFile() return nil } @@ -604,6 +675,8 @@ func (pm *ProcessManager) Restart(ctx context.Context) error { if err := pm.stopLocked(ctx); err != nil { return fmt.Errorf("停止失败: %w", err) } + // 等待端口完全释放 + time.Sleep(1 * time.Second) if err := pm.startLocked(ctx); err != nil { return fmt.Errorf("启动失败: %w", err) }