Browse Source

test 心跳

origin/hotfix/hlk-flight
root 4 months ago
parent
commit
595786890c
  1. 36
      app/core/websocket/manager.py
  2. 36
      modify.md
  3. 236
      test_heartbeat_function.py

36
app/core/websocket/manager.py

@ -49,18 +49,29 @@ class WebSocketManager:
logger.warning(f"WebSocket客户端 {name} 已存在")
return self._clients[name]
# 创建客户端
client = WebSocketClient(url, name)
self._clients[name] = client
try:
# 创建客户端
client = WebSocketClient(url, name)
self._clients[name] = client
# 创建3个Channel
await self._create_client_channels(name)
# 创建3个Channel
await self._create_client_channels(name)
# 启动心跳任务
await self._start_heartbeat_task(name, heartbeat_interval)
# 启动心跳任务 - 确保即使其他步骤失败也要尝试启动心跳
try:
await self._start_heartbeat_task(name, heartbeat_interval)
except Exception as e:
logger.error(f"心跳任务启动失败,但继续创建客户端: {name} - {e}")
logger.info(f"WebSocket管理器创建客户端: {name} -> {url}")
return client
logger.info(f"WebSocket管理器创建客户端: {name} -> {url}")
return client
except Exception as e:
# 如果创建过程中出现异常,清理已创建的资源
if name in self._clients:
del self._clients[name]
logger.error(f"WebSocket管理器创建客户端失败: {name} - {e}")
raise
async def _create_client_channels(self, client_name: str):
"""为客户端创建3个Channel
@ -148,12 +159,17 @@ class WebSocketManager:
单一职责只负责心跳数据生成不处理业务逻辑
"""
logger.info(f"心跳循环开始: {client_name} 间隔:{heartbeat_interval}")
try:
heartbeat_channel = self._channels.get(f"{client_name}_heartbeat")
if not heartbeat_channel:
logger.error(f"心跳Channel不存在: {client_name}_heartbeat")
# 等待一段时间后重试,而不是直接返回
await asyncio.sleep(5)
return
logger.info(f"心跳循环启动成功: {client_name} -> {heartbeat_channel.name}")
while client_name in self._clients and self._clients[client_name].is_connected:
try:
# 创建心跳消息
@ -184,6 +200,8 @@ class WebSocketManager:
except Exception as e:
logger.error(f"心跳任务异常: {client_name} - {e}")
finally:
logger.info(f"心跳循环结束: {client_name}")
async def remove_client(self, name: str) -> bool:
"""移除WebSocket客户端

36
modify.md

@ -1583,3 +1583,39 @@ client = await websocket_manager.create_client("test_1", "ws://localhost:8080",
```
**结论**: 通过添加心跳生成器,解决了心跳Channel没有数据源的问题。现在心跳功能完全自动化,支持可配置的心跳间隔,并且具有完整的生命周期管理。
## 2025-08-12 - WebSocket心跳任务启动问题修复
### 问题描述
在`create_and_connect_client`成功之后,心跳循环`_heartbeat_loop`没有被正确启动,导致没有心跳消息发送。
### 问题分析
1. 在`create_client`方法中,心跳任务启动被放在了最后
2. 如果在创建适配器过程中出现异常,整个方法会抛出异常,导致心跳任务启动代码不会执行
3. 心跳循环缺少详细的日志记录,难以排查问题
### 解决方案
1. **修改`create_client`方法**
- 添加异常处理,确保即使其他步骤失败也要尝试启动心跳任务
- 在心跳任务启动失败时记录错误但继续创建客户端
- 添加资源清理逻辑
2. **增强`_heartbeat_loop`方法**
- 添加详细的日志记录,包括开始、成功启动、结束等状态
- 改进心跳Channel不存在时的处理逻辑
- 添加finally块确保日志记录完整
### 修改文件
- `app/core/websocket/manager.py`
- 修改`create_client`方法的异常处理逻辑
- 增强`_heartbeat_loop`方法的日志记录和错误处理
### 预期效果
- 心跳任务能够正确启动并记录详细日志
- 即使其他组件创建失败,心跳任务也会尝试启动
- 更容易排查心跳相关的问题
### 测试建议
1. 创建WebSocket客户端并检查日志中是否出现心跳任务启动信息
2. 验证心跳消息是否正常发送
3. 测试异常情况下的心跳任务启动

236
test_heartbeat_function.py

@ -1,125 +1,167 @@
#!/usr/bin/env python3
"""
测试心跳功能
测试WebSocket心跳任务启动功能
验证心跳任务是否正确启动并发送心跳消息
"""
import asyncio
import sys
import os
import json
from datetime import datetime
# 添加项目路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from app.core.websocket.manager import websocket_manager
from app.schemas.websocket import CreateWebSocketClientRequest
from app.utils.structured_log import get_structured_logger, LogLevel
logger = get_structured_logger(__name__, LogLevel.INFO)
logger = get_structured_logger(__name__, LogLevel.DEBUG)
async def test_heartbeat_function():
"""测试心跳功能"""
logger.info("开始测试心跳功能")
async def test_heartbeat_task_startup():
"""测试心跳任务启动"""
print("=== 测试心跳任务启动 ===")
try:
# 1. 创建客户端(使用较短的心跳间隔进行测试)
logger.info("1. 创建客户端")
client = await websocket_manager.create_client("test_heartbeat", "ws://localhost:8080", heartbeat_interval=5)
logger.info(f"客户端创建成功: {client.name}")
# 2. 检查初始状态
logger.info("2. 检查初始状态")
stats = websocket_manager.get_stats()
logger.info(f"客户端数量: {stats['client_count']}")
logger.info(f"Channel数量: {stats['channel_count']}")
logger.info(f"适配器数量: {stats['adapter_count']}")
logger.info(f"心跳任务数量: {stats['heartbeat_task_count']}")
# 3. 连接客户端
logger.info("3. 连接客户端")
success = await websocket_manager.connect_client("test_heartbeat")
logger.info(f"客户端连接成功: {success}")
# 4. 等待心跳发送(等待10秒,应该能看到2次心跳)
logger.info("4. 等待心跳发送(10秒)")
for i in range(10):
await asyncio.sleep(1)
logger.info(f"等待中... {i+1}/10")
# 检查心跳Channel的状态
heartbeat_channel = websocket_manager.get_channel("test_heartbeat_heartbeat")
if heartbeat_channel:
stats = heartbeat_channel.get_stats()
logger.info(f"心跳Channel状态: 队列大小={stats['total_queue_size']}, 消息数={stats['message_count']}")
# 5. 手动发送一次心跳
logger.info("5. 手动发送心跳")
success = await websocket_manager.send_heartbeat("test_heartbeat")
logger.info(f"手动心跳发送成功: {success}")
# 6. 检查心跳Channel状态
logger.info("6. 检查心跳Channel状态")
heartbeat_channel = websocket_manager.get_channel("test_heartbeat_heartbeat")
# 测试创建客户端(不实际连接)
client_name = "test_heartbeat_client"
client_url = "wss://localhost:8080"
heartbeat_interval = 10 # 10秒心跳间隔,便于测试
print(f"创建客户端: {client_name} -> {client_url}")
print(f"心跳间隔: {heartbeat_interval}")
# 创建客户端
client = await websocket_manager.create_client(client_name, client_url, heartbeat_interval)
if client:
print(f"✅ 客户端创建成功: {client.name}")
print(f" 状态: {client.state.value}")
print(f" 连接状态: {client.is_connected}")
else:
print("❌ 客户端创建失败")
return False
# 检查心跳任务是否存在
if client_name in websocket_manager._heartbeat_tasks:
heartbeat_task = websocket_manager._heartbeat_tasks[client_name]
print(f"✅ 心跳任务存在: {heartbeat_task}")
print(f" 任务状态: {heartbeat_task.done()}")
print(f" 任务取消状态: {heartbeat_task.cancelled()}")
else:
print("❌ 心跳任务不存在")
return False
# 检查心跳Channel是否存在
heartbeat_channel_name = f"{client_name}_heartbeat"
heartbeat_channel = websocket_manager.get_channel(heartbeat_channel_name)
if heartbeat_channel:
stats = heartbeat_channel.get_stats()
logger.info(f"心跳Channel最终状态: {stats}")
# 7. 移除客户端
logger.info("7. 移除客户端")
success = await websocket_manager.remove_client("test_heartbeat")
logger.info(f"客户端移除成功: {success}")
# 8. 检查最终状态
logger.info("8. 检查最终状态")
stats = websocket_manager.get_stats()
logger.info(f"最终状态: 客户端={stats['client_count']}, Channel={stats['channel_count']}, 适配器={stats['adapter_count']}, 心跳任务={stats['heartbeat_task_count']}")
logger.info("✅ 心跳功能测试完成")
print(f"✅ 心跳Channel存在: {heartbeat_channel.name}")
print(f" 连接状态: {heartbeat_channel.is_connected}")
else:
print(f"❌ 心跳Channel不存在: {heartbeat_channel_name}")
return False
# 等待一段时间,观察心跳消息
print(f"等待 {heartbeat_interval + 2} 秒观察心跳消息...")
await asyncio.sleep(heartbeat_interval + 2)
# 检查心跳任务是否仍在运行
if client_name in websocket_manager._heartbeat_tasks:
heartbeat_task = websocket_manager._heartbeat_tasks[client_name]
if not heartbeat_task.done():
print("✅ 心跳任务仍在运行")
else:
print("❌ 心跳任务已结束")
if heartbeat_task.exception():
print(f" 异常: {heartbeat_task.exception()}")
else:
print("❌ 心跳任务不存在")
# 清理资源
print("清理资源...")
await websocket_manager.remove_client(client_name)
print("✅ 资源清理完成")
return True
except Exception as e:
logger.error(f"❌ 心跳功能测试失败: {e}")
raise
print(f"❌ 测试失败: {e}")
import traceback
traceback.print_exc()
return False
async def test_heartbeat_without_connection():
"""测试未连接状态下的心跳"""
logger.info("开始测试未连接状态下的心跳")
async def test_heartbeat_with_connection():
"""测试带连接的心跳功能"""
print("\n=== 测试带连接的心跳功能 ===")
try:
# 1. 创建客户端但不连接
logger.info("1. 创建客户端但不连接")
client = await websocket_manager.create_client("test_heartbeat_no_conn", "ws://localhost:8080", heartbeat_interval=3)
logger.info(f"客户端创建成功: {client.name}")
# 2. 等待一段时间,看心跳任务是否运行
logger.info("2. 等待心跳任务运行(6秒)")
for i in range(6):
await asyncio.sleep(1)
logger.info(f"等待中... {i+1}/6")
# 检查心跳Channel的状态
heartbeat_channel = websocket_manager.get_channel("test_heartbeat_no_conn_heartbeat")
if heartbeat_channel:
stats = heartbeat_channel.get_stats()
logger.info(f"心跳Channel状态: 队列大小={stats['total_queue_size']}, 消息数={stats['message_count']}")
# 3. 移除客户端
logger.info("3. 移除客户端")
success = await websocket_manager.remove_client("test_heartbeat_no_conn")
logger.info(f"客户端移除成功: {success}")
logger.info("✅ 未连接状态心跳测试完成")
# 测试创建并连接客户端
client_name = "test_heartbeat_connected"
client_url = "wss://192.168.13.15:7268/ws" # 使用实际的WebSocket服务器
heartbeat_interval = 15 # 15秒心跳间隔
print(f"创建并连接客户端: {client_name} -> {client_url}")
print(f"心跳间隔: {heartbeat_interval}")
# 创建客户端
client = await websocket_manager.create_client(client_name, client_url, heartbeat_interval)
if not client:
print("❌ 客户端创建失败")
return False
# 连接客户端
success = await websocket_manager.connect_client(client_name)
if not success:
print("❌ 客户端连接失败")
return False
print("✅ 客户端连接成功")
# 等待一段时间观察心跳
print(f"等待 {heartbeat_interval + 5} 秒观察心跳...")
await asyncio.sleep(heartbeat_interval + 5)
# 检查客户端状态
client = websocket_manager.get_client(client_name)
if client and client.is_connected:
print("✅ 客户端仍保持连接")
else:
print("❌ 客户端连接已断开")
# 清理资源
print("清理资源...")
await websocket_manager.remove_client(client_name)
print("✅ 资源清理完成")
return True
except Exception as e:
logger.error(f"❌ 未连接状态心跳测试失败: {e}")
raise
print(f"❌ 测试失败: {e}")
import traceback
traceback.print_exc()
return False
async def main():
"""主测试函数"""
logger.info("开始心跳功能测试")
print("开始测试WebSocket心跳任务启动功能")
print("=" * 50)
try:
# 测试正常心跳功能
await test_heartbeat_function()
# 测试1:心跳任务启动
success1 = await test_heartbeat_task_startup()
# 测试未连接状态下的心跳
await test_heartbeat_without_connection()
# 测试2:带连接的心跳功能
success2 = await test_heartbeat_with_connection()
logger.info("🎉 所有心跳测试完成")
print("\n" + "=" * 50)
print("测试结果汇总:")
print(f"心跳任务启动测试: {'✅ 通过' if success1 else '❌ 失败'}")
print(f"带连接的心跳测试: {'✅ 通过' if success2 else '❌ 失败'}")
except Exception as e:
logger.error(f"❌ 测试失败: {e}")
raise
if success1 and success2:
print("🎉 所有测试通过!")
else:
print("⚠️ 部分测试失败,请检查日志")
if __name__ == "__main__":
asyncio.run(main())
Loading…
Cancel
Save