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:
+41
-2
@@ -39,6 +39,13 @@ func (a *App) setConnButtons(connectEnabled, disconnectEnabled bool) {
|
||||
} else {
|
||||
a.disconnectBtn.Disable()
|
||||
}
|
||||
if a.profileSelect != nil {
|
||||
if connectEnabled {
|
||||
a.profileSelect.Enable()
|
||||
} else {
|
||||
a.profileSelect.Disable()
|
||||
}
|
||||
}
|
||||
a.setupTray()
|
||||
}
|
||||
|
||||
@@ -132,8 +139,13 @@ func (a *App) onConnect() {
|
||||
}
|
||||
|
||||
a.mu.Lock()
|
||||
oldClient := a.ipcClient
|
||||
a.ipcClient = client
|
||||
a.mu.Unlock()
|
||||
if oldClient != nil {
|
||||
_ = ipc.SendStop(oldClient)
|
||||
oldClient.Close()
|
||||
}
|
||||
|
||||
// Get password from keychain.
|
||||
password, err := a.kc.GetPassword(a.currentProfile.Name)
|
||||
@@ -190,13 +202,24 @@ func (a *App) onConnect() {
|
||||
func (a *App) onDisconnect() {
|
||||
a.mu.Lock()
|
||||
client := a.ipcClient
|
||||
a.ipcClient = nil
|
||||
a.mu.Unlock()
|
||||
if client == nil {
|
||||
return
|
||||
}
|
||||
_ = ipc.SendStop(client)
|
||||
client.Close()
|
||||
a.setConnButtons(true, false)
|
||||
a.stateLabel.SetText(i18n.T("StateDisconnected"))
|
||||
a.ipLabel.SetText(i18n.T("IpNone"))
|
||||
a.ip6Label.SetText(i18n.T("Ip6None"))
|
||||
a.uptimeLabel.SetText(i18n.T("UptimeNone"))
|
||||
a.rxV4Label.SetText(i18n.T("RxV4Zero"))
|
||||
a.txV4Label.SetText(i18n.T("TxV4Zero"))
|
||||
a.rxV6Label.SetText(i18n.T("RxV6Zero"))
|
||||
a.txV6Label.SetText(i18n.T("TxV6Zero"))
|
||||
a.rxTotalLabel.SetText(i18n.T("RxTotalZero"))
|
||||
a.txTotalLabel.SetText(i18n.T("TxTotalZero"))
|
||||
}
|
||||
|
||||
// eventLoop reads IPC events from the daemon and updates the UI.
|
||||
@@ -234,17 +257,33 @@ func (a *App) eventLoop() {
|
||||
switch ev.Event {
|
||||
case ipc.EvState:
|
||||
fyne.Do(func() {
|
||||
a.applyState(ev.State)
|
||||
a.mu.Lock()
|
||||
current := a.ipcClient
|
||||
a.mu.Unlock()
|
||||
if current == client {
|
||||
a.applyState(ev.State)
|
||||
}
|
||||
})
|
||||
case ipc.EvStats:
|
||||
if ev.Stats != nil {
|
||||
s := *ev.Stats
|
||||
fyne.Do(func() {
|
||||
a.applyStats(s)
|
||||
a.mu.Lock()
|
||||
current := a.ipcClient
|
||||
a.mu.Unlock()
|
||||
if current == client {
|
||||
a.applyStats(s)
|
||||
}
|
||||
})
|
||||
}
|
||||
case ipc.EvError:
|
||||
fyne.Do(func() {
|
||||
a.mu.Lock()
|
||||
current := a.ipcClient
|
||||
a.mu.Unlock()
|
||||
if current != client {
|
||||
return
|
||||
}
|
||||
if ev.Code == "tls_error" {
|
||||
showError(i18n.T("DlgTLSError"),
|
||||
i18n.T("TLSErrorVerification")+"\n\n"+ev.Message, a.window)
|
||||
|
||||
Reference in New Issue
Block a user