diff --git a/internal/process/manager.go b/internal/process/manager.go index 20ba30b..e934ef5 100644 --- a/internal/process/manager.go +++ b/internal/process/manager.go @@ -486,6 +486,34 @@ func (pm *ProcessManager) killProcess(pid int) error { return process.Kill() } +// killAllFrpc 强制杀死所有 frpc 进程(包括孤儿) +func (pm *ProcessManager) killAllFrpc() { + log.Printf("[INFO] 清理所有 frpc 进程...") + if runtime.GOOS == "windows" { + exec.Command("taskkill", "/F", "/IM", "frpc.exe").Run() + } else { + exec.Command("pkill", "-9", "-f", "frpc").Run() + } + time.Sleep(1 * time.Second) + pm.deletePIDFile() +} + +// countFrpcProcesses 统计当前 frpc 进程数量 +func (pm *ProcessManager) countFrpcProcesses() int { + if runtime.GOOS == "windows" { + cmd := exec.Command("tasklist", "/FI", "IMAGENAME eq frpc.exe") + out, _ := cmd.CombinedOutput() + return strings.Count(string(out), "frpc.exe") + } + cmd := exec.Command("pgrep", "-c", "-f", "frpc") + out, err := cmd.Output() + if err != nil { + return 0 + } + count, _ := strconv.Atoi(strings.TrimSpace(string(out))) + return count +} + func (pm *ProcessManager) cleanupOrphans() { result := pm.CheckPort() if !result.Ready { @@ -619,7 +647,6 @@ func (pm *ProcessManager) computeState(pid int, result PortCheckResult) ProcessS stderr := pm.getLastError() frpReady := pm.isFRPReady(stdout) - // 获取退出码 pm.exitMu.Lock() exitCode := pm.exitCode pm.exitMu.Unlock() @@ -649,7 +676,6 @@ func (pm *ProcessManager) computeState(pid int, result PortCheckResult) ProcessS return state } - // 异常退出 if exitCode != 0 && exitCode != -1 { state.Error = fmt.Sprintf("进程异常退出 (exit code: %d)", exitCode) if stderr != "" { @@ -677,6 +703,11 @@ func (pm *ProcessManager) computeState(pid int, result PortCheckResult) ProcessS if portReady && frpReady { state.Phase = PhaseRunning + // 检测多进程 + if count := pm.countFrpcProcesses(); count > 1 { + state.Phase = PhaseDegraded + state.Error = fmt.Sprintf("检测到 %d 个 frpc 进程,可能存在多开", count) + } return state } @@ -761,7 +792,12 @@ func (pm *ProcessManager) Start(ctx context.Context) error { } defer pm.Unlock() + // 先杀所有 frpc 进程(避免多开) + pm.killAllFrpc() + + // 清理孤儿 pm.cleanupOrphans() + return pm.startLocked(ctx) } @@ -779,7 +815,6 @@ func (pm *ProcessManager) startLocked(ctx context.Context) error { pm.startTime = time.Now() pm.expectedStop = false - // 重置退出码 pm.exitMu.Lock() pm.exitCode = -1 pm.exitMu.Unlock() @@ -847,15 +882,18 @@ func (pm *ProcessManager) startLocked(ctx context.Context) error { time.Sleep(StartupRetryDelay) } + // 超时:检查进程状态 if pm.isProcessAlive(cmd.Process.Pid) { stderr := pm.getLastError() log.Printf("[WARN] frpc 启动超时 (PID: %d),当前 stderr: %s", cmd.Process.Pid, stderr) - // 进程还在但端口没起来,尝试获取 frpc admin API 版本信息 if info := pm.getFRPCStatus(cmd.Process.Pid); info != nil && info.Version != "" { log.Printf("[INFO] frpc admin API 可访问,版本: %s", info.Version) - // API 可访问说明 frpc 已正常运行,只是端口检测有误 + pm.writePIDFile(cmd.Process.Pid) return nil } + // 启动失败:杀掉进程 + pm.killProcess(cmd.Process.Pid) + pm.deletePIDFile() return fmt.Errorf("frpc 启动超时: 进程存在但端口未就绪") } @@ -957,6 +995,7 @@ func (pm *ProcessManager) HealthCheck() map[string]interface{} { result["phase"] = state.Phase result["pid"] = state.PID + result["port"] = state.Port result["alive"] = state.Alive result["port_ready"] = state.PortReady result["frp_ready"] = state.FRPReady