fix: worker 进程并发 double-close panic + 状态页连接语义重构 + 集群页骨架

- process: spawn 不再重复起 supervise goroutine(Restart 触发旧 supervise close doneCh 后再 close → panic);新增 closeOnce/supervising 守卫;Start 首次起 supervise,重启循环在同一 goroutine 内

- httpapi: status 增加 connState(connected/connecting/failed/not_started/disabled),由 worker 日志探测真实 frps 连接(不再把本地 worker 状态误当远程 frps 状态)

- StatusView: 远程卡片主状态改为连接状态徽标;启停按钮标注为控制本地 frpc worker;本地转发区保留 worker 状态显示

- 新增 ClusterView 集群页骨架(M3 LB+健康检查载体)+ App.vue 导航接入

- render: bandwidthLimit 从 proxy 顶层移入 transport 段(frpc 0.61 正确 schema,修复真实连不上的 bug)
This commit is contained in:
2026-08-17 15:13:25 +08:00
parent 58906c5a81
commit bd3a62a017
12 changed files with 221 additions and 57 deletions

View File

@ -47,12 +47,14 @@ type worker struct {
proc *exec.Cmd
logFile *rotatingFile
stopOnce sync.Once
stopCh chan struct{}
doneCh chan struct{}
stopOnce sync.Once
closeOnce sync.Once
stopCh chan struct{}
doneCh chan struct{}
mu sync.Mutex
status Status
mu sync.Mutex
status Status
supervising bool // guarded by Manager.mu
}
// Manager supervises all workers.
@ -103,6 +105,12 @@ func (m *Manager) Start(name string) error {
m.mu.Unlock()
return err
}
m.mu.Lock()
if !w.supervising {
w.supervising = true
go m.supervise(w)
}
m.mu.Unlock()
return nil
}
@ -145,12 +153,13 @@ func (m *Manager) spawn(w *worker) error {
w.proc = cmd
w.logFile = logWriter
m.setState(w, Status{State: "running", Pid: cmd.Process.Pid, StartTime: time.Now().Unix(), RestartCount: restartCount})
go m.supervise(w)
// NOTE: no new supervise goroutine here. The first Start()'s supervise loop
// re-spawns on restart; spawning here would race on doneCh close.
return nil
}
func (m *Manager) supervise(w *worker) {
defer close(w.doneCh)
defer w.closeOnce.Do(func() { close(w.doneCh) })
for {
err := w.proc.Wait()
exitCode := 0