🐛 修復 websocket 不正常斷開問題

This commit is contained in:
Minidoracat 2025-06-06 22:29:49 +08:00
parent ad44568c7c
commit 466dcffaa9
4 changed files with 117 additions and 31 deletions

View File

@ -332,6 +332,9 @@ class WebUIManager:
old_websocket = self._old_websocket_for_update
new_session = self._new_session_for_update
# 檢查舊連接是否仍然有效
if old_websocket and not old_websocket.client_state.DISCONNECTED:
try:
# 發送會話更新通知
await old_websocket.send_json({
"type": "session_updated",
@ -344,18 +347,19 @@ class WebUIManager:
})
debug_log("已通過舊 WebSocket 連接發送會話更新通知")
# 延遲一小段時間讓前端處理消息
await asyncio.sleep(0.2)
except Exception as send_error:
debug_log(f"發送會話更新通知失敗: {send_error}")
# 安全關閉舊連接
await self._safe_close_websocket(old_websocket)
# 清理臨時變數
delattr(self, '_old_websocket_for_update')
delattr(self, '_new_session_for_update')
# 延遲一小段時間讓前端處理消息,然後關閉舊連接
await asyncio.sleep(0.1)
try:
await old_websocket.close()
debug_log("已關閉舊 WebSocket 連接")
except Exception as e:
debug_log(f"關閉舊 WebSocket 連接失敗: {e}")
else:
# 沒有舊連接,設置待更新標記
self._pending_session_update = True
@ -366,6 +370,31 @@ class WebUIManager:
# 回退到待更新標記
self._pending_session_update = True
async def _safe_close_websocket(self, websocket):
"""安全關閉 WebSocket 連接,避免事件循環衝突"""
if not websocket:
return
try:
# 檢查連接狀態
if websocket.client_state.DISCONNECTED:
debug_log("WebSocket 已斷開,跳過關閉操作")
return
# 嘗試正常關閉
await asyncio.wait_for(websocket.close(code=1000, reason="會話更新"), timeout=2.0)
debug_log("已正常關閉舊 WebSocket 連接")
except asyncio.TimeoutError:
debug_log("WebSocket 關閉超時,強制斷開")
except RuntimeError as e:
if "attached to a different loop" in str(e):
debug_log(f"WebSocket 事件循環衝突,忽略關閉錯誤: {e}")
else:
debug_log(f"WebSocket 關閉時發生運行時錯誤: {e}")
except Exception as e:
debug_log(f"關閉 WebSocket 連接時發生未知錯誤: {e}")
async def _check_active_tabs(self) -> bool:
"""檢查是否有活躍標籤頁 - 優先檢查全局狀態,回退到 API"""
try:

View File

@ -324,7 +324,9 @@ class WebFeedbackSession:
"message": "會話已超時,介面將自動關閉"
})
await asyncio.sleep(0.1) # 給前端一點時間處理消息
await self.websocket.close()
# 安全關閉 WebSocket
await self._safe_close_websocket()
debug_log(f"會話 {self.session_id} WebSocket 已關閉")
except Exception as e:
debug_log(f"關閉 WebSocket 時發生錯誤: {e}")
@ -402,3 +404,28 @@ class WebFeedbackSession:
# 設置完成事件
self.feedback_completed.set()
async def _safe_close_websocket(self):
"""安全關閉 WebSocket 連接,避免事件循環衝突"""
if not self.websocket:
return
try:
# 檢查連接狀態
if hasattr(self.websocket, 'client_state') and self.websocket.client_state.DISCONNECTED:
debug_log("WebSocket 已斷開,跳過關閉操作")
return
# 嘗試正常關閉
await asyncio.wait_for(self.websocket.close(code=1000, reason="會話清理"), timeout=2.0)
debug_log(f"會話 {self.session_id} WebSocket 已正常關閉")
except asyncio.TimeoutError:
debug_log(f"會話 {self.session_id} WebSocket 關閉超時")
except RuntimeError as e:
if "attached to a different loop" in str(e):
debug_log(f"會話 {self.session_id} WebSocket 事件循環衝突,忽略關閉錯誤: {e}")
else:
debug_log(f"會話 {self.session_id} WebSocket 關閉時發生運行時錯誤: {e}")
except Exception as e:
debug_log(f"會話 {self.session_id} 關閉 WebSocket 時發生未知錯誤: {e}")

View File

@ -192,11 +192,16 @@ def setup_routes(manager: 'WebUIManager'):
await handle_websocket_message(manager, session, message)
except WebSocketDisconnect:
debug_log(f"WebSocket 連接斷開")
debug_log(f"WebSocket 連接正常斷開")
except ConnectionResetError:
debug_log(f"WebSocket 連接被重置")
except Exception as e:
debug_log(f"WebSocket 錯誤: {e}")
finally:
# 安全清理 WebSocket 連接
if session.websocket == websocket:
session.websocket = None
debug_log("已清理會話中的 WebSocket 連接")
@manager.app.post("/api/save-settings")
async def save_settings(request: Request):

View File

@ -800,7 +800,9 @@ class FeedbackApp {
* 檢查是否可以提交回饋
*/
canSubmitFeedback() {
return this.feedbackState === 'waiting_for_feedback' && this.isConnected;
const canSubmit = this.feedbackState === 'waiting_for_feedback' && this.isConnected;
console.log(`🔍 檢查提交權限: feedbackState=${this.feedbackState}, isConnected=${this.isConnected}, canSubmit=${canSubmit}`);
return canSubmit;
}
/**
@ -989,6 +991,12 @@ class FeedbackApp {
// 停止心跳
this.stopWebSocketHeartbeat();
// 重置回饋狀態,避免卡在處理狀態
if (this.feedbackState === 'processing') {
console.log('🔄 WebSocket 斷開,重置處理狀態');
this.setFeedbackState('waiting_for_feedback');
}
if (event.code === 4004) {
// 沒有活躍會話
this.updateConnectionStatus('disconnected', '沒有活躍會話');
@ -998,7 +1006,10 @@ class FeedbackApp {
// 只有在非正常關閉時才重連
if (event.code !== 1000) {
console.log('3秒後嘗試重連...');
setTimeout(() => this.setupWebSocket(), 3000);
setTimeout(() => {
console.log('🔄 開始重連 WebSocket...');
this.setupWebSocket();
}, 3000);
}
}
};
@ -1107,12 +1118,16 @@ class FeedbackApp {
// 顯示更新通知
this.showSuccessMessage(data.message || '會話已更新,正在局部更新內容...');
// 重置回饋狀態為等待新回饋
this.setFeedbackState('waiting_for_feedback');
// 更新會話信息
if (data.session_info) {
this.currentSessionId = data.session_info.session_id;
const newSessionId = data.session_info.session_id;
console.log(`📋 會話 ID 更新: ${this.currentSessionId} -> ${newSessionId}`);
// 重置回饋狀態為等待新回饋(使用新的會話 ID
this.setFeedbackState('waiting_for_feedback', newSessionId);
// 更新當前會話 ID
this.currentSessionId = newSessionId;
// 更新頁面標題
if (data.session_info.project_directory) {
@ -1122,6 +1137,10 @@ class FeedbackApp {
// 使用局部更新替代整頁刷新
this.refreshPageContent();
} else {
// 如果沒有會話信息,仍然重置狀態
console.log('⚠️ 會話更新沒有包含會話信息,僅重置狀態');
this.setFeedbackState('waiting_for_feedback');
}
console.log('✅ 會話更新處理完成');
@ -1576,16 +1595,22 @@ class FeedbackApp {
// 所有事件監聽器已在 setupEventListeners() 中統一設置
submitFeedback() {
console.log('📤 嘗試提交回饋...');
// 檢查是否可以提交回饋
if (!this.canSubmitFeedback()) {
console.log('⚠️ 無法提交回饋 - 當前狀態:', this.feedbackState);
console.log('⚠️ 無法提交回饋 - 當前狀態:', this.feedbackState, '連接狀態:', this.isConnected);
if (this.feedbackState === 'feedback_submitted') {
this.showMessage('回饋已提交,請等待下次 MCP 調用', 'warning');
} else if (this.feedbackState === 'processing') {
this.showMessage('正在處理中,請稍候', 'warning');
} else if (!this.isConnected) {
this.showMessage('WebSocket 未連接', 'error');
this.showMessage('WebSocket 未連接,正在嘗試重連...', 'error');
// 嘗試重新建立連接
this.setupWebSocket();
} else {
this.showMessage(`當前狀態不允許提交: ${this.feedbackState}`, 'warning');
}
return;
}