fix: 修复连接状态下切换配置后断开重连导致异常的问题
问题现象:连接中切换配置 -> 断开 -> 连接,会出现连接后瞬间断开、 长时间无法重连。而先断开再切换配置则正常。 根因分析(三个层面的资源泄漏叠加): 1. pumpPackets 死锁 (session.go) Disconnect() 只关闭 WebSocket transport,不关闭 TUN 设备。 TUN->WS goroutine 阻塞在 dev.Read(),pumpPackets 的 wg.Wait() 永远不返回,cleanup()(关闭 TUN/删路由)无法执行,导致 TUN 设备和路由泄漏。 2. 僵尸 eventLoop + 僵尸 IPC 连接 (view.go) onDisconnect 不清空 ipcClient、不关闭 IPC 连接,旧 eventLoop 持续运行接收广播事件。eventLoop 正常事件处理无身份校验, 旧 session 的 disconnected 广播会覆盖新 session 的 connected 状态。 3. stopSession 不等待 goroutine 退出 (daemon.go) d.session = nil 在 goroutine 仍在运行时即设置,新旧 session 并发执行导致 TUN/路由冲突。无 mutex 保护并发访问。 修复内容(10 项,3 个文件): session.go: - Disconnect() 新增 dev.Close() 解除 pumpPackets 死锁 - 新增 done channel,Disconnect() 阻塞等待 run goroutine 完全退出 - run() deferred setState 跳过用户主动断开时的冗余广播 - run() 顶部 ctx.Err() 检查后补 cleanup() daemon.go: - 新增 sync.Mutex 保护 session/cancel 并发访问 - 拆分 stopSession/stopSessionLocked 避免死锁 view.go: - onDisconnect 置 ipcClient=nil + client.Close() 终止僵尸 eventLoop - eventLoop 三个事件分支均加 if current == client 身份校验 - onConnect 覆盖前清理旧 IPC client - setConnButtons 连接时禁用配置下拉框
This commit is contained in:
+16
-2
@@ -60,6 +60,7 @@ type SessionManager struct {
|
||||
dev tun.Device
|
||||
routeMgr *route.Manager
|
||||
conn *transport.Conn
|
||||
done chan struct{}
|
||||
|
||||
// EWMA speed smoothing state. Only touched by reportStats (single
|
||||
// goroutine), so no lock needed. ewma* fields hold the smoothed
|
||||
@@ -106,6 +107,7 @@ func (sm *SessionManager) Connect(ctx context.Context, cfg SessionConfig) error
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
sm.cancel = cancel
|
||||
sm.running = true
|
||||
sm.done = make(chan struct{})
|
||||
sm.mu.Unlock()
|
||||
|
||||
go sm.run(ctx, cfg)
|
||||
@@ -126,21 +128,32 @@ func (sm *SessionManager) Disconnect() {
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
// Close the transport to unblock the packet pump.
|
||||
// Close the transport to unblock the WS->TUN goroutine.
|
||||
sm.mu.Lock()
|
||||
conn := sm.conn
|
||||
dev := sm.dev
|
||||
sm.mu.Unlock()
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
// Close the TUN device to unblock the TUN->WS goroutine's dev.Read().
|
||||
if dev != nil {
|
||||
dev.Close()
|
||||
}
|
||||
// Wait for the run goroutine to fully exit so that cleanup
|
||||
// (route removal, TUN teardown) is complete before returning.
|
||||
if sm.done != nil {
|
||||
<-sm.done
|
||||
}
|
||||
}
|
||||
|
||||
// run is the main session loop with exponential-backoff reconnection
|
||||
// and CDN IP failover.
|
||||
func (sm *SessionManager) run(ctx context.Context, cfg SessionConfig) {
|
||||
fatal := false
|
||||
defer close(sm.done)
|
||||
defer func() {
|
||||
if !fatal {
|
||||
if !fatal && ctx.Err() == nil {
|
||||
sm.setState(stats.StateDisconnected)
|
||||
}
|
||||
}()
|
||||
@@ -154,6 +167,7 @@ func (sm *SessionManager) run(ctx context.Context, cfg SessionConfig) {
|
||||
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
sm.cleanup()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user