部分逻辑优化,涉及进程检测代码

This commit is contained in:
2026-08-07 22:35:08 +08:00
parent d7f3ffd476
commit 5f028dae4c
2 changed files with 92 additions and 19 deletions
+87 -14
View File
@@ -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)
}