diff --git a/internal/process/manager.go b/internal/process/manager.go index 71bdaa7..9f4dc45 100644 --- a/internal/process/manager.go +++ b/internal/process/manager.go @@ -399,7 +399,6 @@ func (pm *ProcessManager) isFRPCProcess(pid int) (bool, string) { // 进程存活检测 // ================================================================ -// isProcessAlive 检查 PID 对应的进程是否存活 func (pm *ProcessManager) isProcessAlive(pid int) bool { if pid <= 0 { return false @@ -421,6 +420,50 @@ func (pm *ProcessManager) isProcessAlive(pid int) bool { return process.Signal(syscall.Signal(0)) == nil } +// ================================================================ +// 进程清理 +// ================================================================ + +func (pm *ProcessManager) killProcess(pid int) error { + if pid <= 0 { + return nil + } + + if runtime.GOOS == "windows" { + cmd := exec.Command("taskkill", "/F", "/PID", strconv.Itoa(pid)) + return cmd.Run() + } + + process, err := os.FindProcess(pid) + if err != nil { + return err + } + return process.Kill() +} + +func (pm *ProcessManager) cleanupOrphans() { + // 检查端口是否被占用 + occupied, err := pm.CheckPort() + if err != nil || !occupied { + return + } + + // 检查是否有 PID 文件 + pid := pm.readPIDFile() + if pid > 0 && pm.isProcessAlive(pid) { + return + } + + log.Printf("[WARN] 检测到孤儿 frpc 进程 (端口 %d 被占用但无有效 PID),正在清理...", pm.adminPort) + if runtime.GOOS == "windows" { + exec.Command("taskkill", "/F", "/IM", "frpc.exe").Run() + } else { + exec.Command("pkill", "-f", "frpc").Run() + } + pm.deletePIDFile() + time.Sleep(1 * time.Second) +} + // ================================================================ // PID 文件操作 // ================================================================ @@ -532,6 +575,10 @@ func (pm *ProcessManager) Start(ctx context.Context) error { return fmt.Errorf("获取锁失败: %w", err) } defer pm.Unlock() + + // 清理孤儿进程 + pm.cleanupOrphans() + return pm.startLocked(ctx) } @@ -557,45 +604,28 @@ func (pm *ProcessManager) startLocked(ctx context.Context) error { return fmt.Errorf("启动 frpc 失败: %w", err) } - // ============================================================ - // 组合状态检测:最多等待 5 秒,三条件全部满足才算成功 - // ============================================================ + // 立即写入 PID 文件 + if err := pm.writePIDFile(cmd.Process.Pid); err != nil { + log.Printf("[WARN] 写入 PID 文件失败: %v", err) + } + log.Printf("[DEBUG] frpc 进程已启动,PID: %d", cmd.Process.Pid) + + // 检测端口是否监听(最多 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)) + cmd.Process.Pid, pm.adminPort, attempt*int(StartupRetryDelay/time.Millisecond)) return nil } - time.Sleep(StartupRetryDelay) } - // 超时:输出诊断信息 - pid := pm.readPIDFile() - if pid > 0 { - alive := pm.isProcessAlive(pid) - occupied, _ := pm.CheckPort() - return fmt.Errorf("frpc 启动超时: PID=%d, 进程存活=%v, 端口监听=%v", pid, alive, occupied) - } - - return fmt.Errorf("frpc 启动超时: 未生成 PID 文件") + // 端口监听超时:杀掉启动的进程并清理 + log.Printf("[WARN] frpc 启动超时 (PID: %d),正在清理...", cmd.Process.Pid) + pm.killProcess(cmd.Process.Pid) + pm.deletePIDFile() + return fmt.Errorf("frpc 启动超时: 端口未监听 (PID: %d)", cmd.Process.Pid) } func (pm *ProcessManager) Stop(ctx context.Context) error { @@ -652,8 +682,7 @@ func (pm *ProcessManager) stopLocked(_ context.Context) error { // 端口未释放, 强制 kill if runtime.GOOS == "windows" { - cmd := exec.Command("taskkill", "/F", "/IM", "frpc.exe") - cmd.Run() + exec.Command("taskkill", "/F", "/IM", "frpc.exe").Run() } else { proc.Kill() }