zlwu před 1 rokem
rodič
revize
9d92a207c8

+ 11 - 0
backend/connection_pool.py

@@ -217,6 +217,17 @@ class DoubaoConnectionPool:
             except Exception as e:
                 logger.error(f"健康检查循环错误: {e}")
     
+    def get_pool_status(self) -> dict:
+        """获取连接池状态"""
+        return {
+            'pool_size': self.pool_size,
+            'idle_connections': self.idle_connections.qsize(),
+            'active_connections': len(self.active_connections),
+            'total_connections': self.idle_connections.qsize() + len(self.active_connections),
+            'health_check_interval': settings.CONNECTION_HEALTH_CHECK_INTERVAL,
+            'max_idle_time': self.max_idle_time,
+        }
+    
     async def close_all(self):
         """关闭所有连接"""
         logger.info("关闭连接池...")

+ 19 - 2
backend/websocket_handler.py

@@ -62,7 +62,7 @@ class StreamingSession:
             if not self.doubao_client:
                 await self.send_error(
                     ErrorCode.CONNECTION_POOL_EXHAUSTED,
-                    "连接池已耗尽"
+                    "连接池已耗尽,请稍后重试"
                 )
                 return False
             
@@ -71,6 +71,10 @@ class StreamingSession:
             
         except Exception as e:
             logger.error(f"启动会话失败: {e}")
+            # 如果获取连接后出错,需要立即释放
+            if self.doubao_client:
+                await connection_pool.release(self.doubao_client)
+                self.doubao_client = None
             await self.send_error(ErrorCode.INTERNAL_ERROR, f"启动会话失败: {str(e)}")
             return False
     
@@ -576,6 +580,8 @@ class StreamingSession:
     async def cleanup(self):
         """清理会话"""
         try:
+            logger.info(f"开始清理会话 {self.session_id}...")
+            
             # 记录会话
             performance_monitor.record_session()
             
@@ -586,15 +592,26 @@ class StreamingSession:
             
             # 注意:ai_response_end已在receive_from_doubao中发送,这里不重复发送
             
-            # 归还连接到池中
+            # 归还连接到池中(关键!必须释放)
             if self.doubao_client:
+                logger.debug(f"释放会话 {self.session_id} 的豆包连接到连接池")
                 await connection_pool.release(self.doubao_client)
                 self.doubao_client = None
+            else:
+                logger.warning(f"会话 {self.session_id} 没有豆包连接需要释放")
             
             logger.info(f"会话 {self.session_id} 已清理 - 收到: {self.audio_received_bytes}B, 发送: {self.audio_sent_bytes}B")
             
         except Exception as e:
             logger.error(f"清理会话失败: {e}")
+            # 即使清理失败,也要尝试释放连接
+            if self.doubao_client:
+                try:
+                    await connection_pool.release(self.doubao_client)
+                    self.doubao_client = None
+                    logger.info(f"在异常处理中成功释放连接")
+                except Exception as release_error:
+                    logger.error(f"释放连接失败: {release_error}")
 
 
 class ConnectionManager:

+ 26 - 5
frontend/src/components/MainPage.tsx

@@ -33,11 +33,13 @@ export default function MainPage() {
   const [isInitialized, setIsInitialized] = useState(false)
   const latencyStartTime = useRef<number>(0)
   const initStarted = useRef(false)  // 防止重复初始化
+  const cleanupInProgress = useRef(false)  // 防止重复清理
 
   // 初始化所有模块
   useEffect(() => {
     // 防止React StrictMode重复执行
     if (initStarted.current) {
+      console.warn('⚠️ 阻止重复初始化')
       return
     }
     initStarted.current = true
@@ -146,11 +148,30 @@ export default function MainPage() {
 
     // 清理函数
     return () => {
-      console.log('清理应用资源...')
-      wsRef.current?.close()
-      recorderRef.current?.close()
-      playerRef.current?.destroy()
-      initStarted.current = false
+      // 防止重复清理
+      if (cleanupInProgress.current) {
+        console.warn('⚠️ 清理已在进行中,跳过')
+        return
+      }
+      cleanupInProgress.current = true
+      
+      console.log('🧹 清理应用资源...')
+      
+      try {
+        wsRef.current?.close()
+        wsRef.current = null
+        
+        recorderRef.current?.close()
+        recorderRef.current = null
+        
+        playerRef.current?.destroy()
+        playerRef.current = null
+      } catch (error) {
+        console.error('清理资源时出错:', error)
+      }
+      
+      // ⚠️ 不要重置 initStarted,避免在StrictMode下重复初始化
+      // initStarted.current = false
     }
   }, [])
 

+ 12 - 0
frontend/src/components/RecordButton.tsx

@@ -44,6 +44,7 @@ export default function RecordButton({ onPress, onRelease, disabled = false }: R
   }
 
   const handleTouchStart = (e: React.TouchEvent) => {
+    e.preventDefault() // 防止触发鼠标事件和默认行为
     console.log('触摸开始 - state:', animationState)
     
     // 允许录音
@@ -64,6 +65,16 @@ export default function RecordButton({ onPress, onRelease, disabled = false }: R
     }
   }
 
+  const handleTouchCancel = (e: React.TouchEvent) => {
+    e.preventDefault()
+    console.log('触摸取消')
+    setIsPressed(false)
+    if (isPressing.current) {
+      isPressing.current = false
+      onRelease()
+    }
+  }
+
   // 阻止长按弹出右键菜单
   const handleContextMenu = (e: React.MouseEvent) => {
     e.preventDefault()
@@ -113,6 +124,7 @@ export default function RecordButton({ onPress, onRelease, disabled = false }: R
         onMouseLeave={handleMouseUp}
         onTouchStart={handleTouchStart}
         onTouchEnd={handleTouchEnd}
+        onTouchCancel={handleTouchCancel}
         onContextMenu={handleContextMenu}
         disabled={disabled || !isConnected}
       >

+ 57 - 0
连接池耗尽修复.md

@@ -0,0 +1,57 @@
+# 连接池耗尽问题修复
+
+## 问题现象
+- 连接池很快被耗尽(10个连接全部占用)
+- 前端只有一个手机在操作,却产生了12个WebSocket连接
+- 出现"连接池已耗尽,等待超时"错误
+
+## 根本原因
+1. **前端重复初始化**:React StrictMode导致组件重复mount/unmount,每次都创建新WebSocket
+2. **连接未正确释放**:cleanup函数重置了`initStarted`标志,导致重新初始化
+3. **后端异常处理不完善**:在start()方法异常时没有释放已获取的连接
+
+## 修复方案
+
+### 前端修复 (`frontend/src/components/MainPage.tsx`)
+1. 添加`cleanupInProgress`标志防止重复清理
+2. 移除cleanup中的`initStarted.current = false`,避免重复初始化
+3. 增强cleanup逻辑,确保资源被正确释放并设置为null
+
+### 后端修复 (`backend/websocket_handler.py`)
+1. `start()`方法:在异常时立即释放已获取的连接
+2. `cleanup()`方法:
+   - 添加详细日志追踪
+   - 增强异常处理,即使清理失败也要释放连接
+   - 添加警告日志,当没有连接需要释放时
+
+### 连接池增强 (`backend/connection_pool.py`)
+- 添加`get_pool_status()`方法,方便监控连接池状态
+
+## 监控方法
+
+### 查看连接池状态
+```bash
+curl http://192.168.2.105:8000/stats
+```
+
+返回示例:
+```json
+{
+  "connection_pool": {
+    "pool_size": 10,
+    "idle": 5,
+    "active": 5
+  }
+}
+```
+
+### 关键日志
+- `开始清理会话 {session_id}...` - 会话开始清理
+- `释放会话 {session_id} 的豆包连接到连接池` - 连接被释放
+- `会话 {session_id} 没有豆包连接需要释放` - 警告:会话没有连接
+
+## 建议
+1. 生产环境关闭React StrictMode(在vite.config.ts中配置)
+2. 提醒用户不要打开多个标签页
+3. 监控连接池状态,及时发现异常
+