Refactor memory analysis with ThreadPoolExecutor for cross-platform timeout

Co-authored-by: jxxghp <jxxghp@163.com>
This commit is contained in:
Cursor Agent
2025-07-09 09:38:06 +00:00
co-authored by jxxghp
parent 29a605f265
commit 028d18826a
3 changed files with 279 additions and 19 deletions
+112
View File
@@ -0,0 +1,112 @@
# 内存分析功能修复说明
## 问题描述
原始的内存分析功能存在严重的性能问题,导致:
1. CPU占用100%,系统卡死
2. 超时机制在守护线程中失效
3. 跨平台兼容性问题(Windows不支持signal.SIGALRM
## 修复内容
### 1. 超时机制重构
**问题**
- 使用 `signal.SIGALRM` 只在主线程中有效
- 在守护线程中无法正常工作
- Windows系统不支持 `signal.SIGALRM`
**解决方案**
- 使用 `concurrent.futures.ThreadPoolExecutor` 替代信号机制
- 实现真正的跨平台超时控制
- 在守护线程中也能正常工作
```python
# 修复前(有问题)
def _run_with_timeout(self, func, *args, **kwargs):
signal.signal(signal.SIGALRM, self._timeout_handler)
signal.alarm(self._analysis_timeout)
# 只在主线程中有效
# 修复后(正确)
def _run_with_timeout(self, func, *args, **kwargs):
future = self._executor.submit(func, *args, **kwargs)
result = future.result(timeout=self._analysis_timeout)
# 跨平台,所有线程都有效
```
### 2. 性能优化
**限制分析对象数量**
- 设置 `_max_objects_to_analyze = 50000`
- 超过限制时使用随机采样
- 避免分析数百万个对象
**优化大对象分析**
- 提高大对象阈值到1MB
- 限制分析数量到30个
- 使用简化的信息获取方法
**简化内存映射分析**
- 移除复杂的文件路径分析
- 只保留基本权限分类
- 减少数据处理量
### 3. 资源管理
**线程池管理**
- 添加 `__del__` 方法确保线程池正确关闭
- 使用单线程池避免资源竞争
- 设置线程名称便于调试
**文件操作优化**
- 减少文件读写次数
- 使用更高效的文件更新方式
- 添加异常处理和错误恢复
## 配置建议
为了进一步控制内存分析的影响,建议在配置文件中设置:
```env
# 默认关闭内存分析,需要时手动开启
MEMORY_ANALYSIS=false
# 增加快照间隔到10分钟
MEMORY_SNAPSHOT_INTERVAL=10
# 减少保留的快照数量
MEMORY_SNAPSHOT_KEEP_COUNT=5
```
## 测试验证
创建了测试脚本 `test_timeout_fix.py` 来验证修复效果:
1. **守护线程超时测试**:验证超时机制在守护线程中的工作情况
2. **性能测试**:监控CPU使用率,确保不会导致系统卡死
3. **并发测试**:测试多个线程同时进行内存分析
## 预期效果
修复后的内存分析功能应该:
- ✅ 不会导致CPU占用100%
- ✅ 不会造成系统卡死
- ✅ 在守护线程中正常工作
- ✅ 跨平台兼容(Windows/Linux/macOS
- ✅ 有合理的超时保护机制
- ✅ 保持核心分析功能完整
## 使用建议
1. **生产环境**:建议默认关闭内存分析功能
2. **调试环境**:可以开启进行内存问题诊断
3. **性能监控**:定期检查内存使用情况,避免内存泄漏
4. **日志监控**:关注内存分析相关的日志信息
## 注意事项
1. 内存分析仍然会消耗一定的CPU和内存资源
2. 建议在系统负载较低时进行详细分析
3. 如果仍然遇到性能问题,可以进一步调整超时时间和对象数量限制
4. 定期清理内存快照文件,避免占用过多磁盘空间
+18 -18
View File
@@ -4,7 +4,6 @@ import threading
import time import time
import os import os
import tracemalloc import tracemalloc
import signal
from datetime import datetime from datetime import datetime
from typing import Optional, Dict, List, Tuple from typing import Optional, Dict, List, Tuple
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError
@@ -45,35 +44,36 @@ class MemoryHelper(metaclass=Singleton):
self._analysis_timeout = 30 # 分析超时时间(秒) self._analysis_timeout = 30 # 分析超时时间(秒)
self._large_object_threshold = 1024 * 1024 # 大对象阈值(1MB self._large_object_threshold = 1024 * 1024 # 大对象阈值(1MB
# 线程池用于超时控制
self._executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="MemoryAnalysis")
# 启用tracemalloc以获得更详细的内存信息 # 启用tracemalloc以获得更详细的内存信息
if not tracemalloc.is_tracing(): if not tracemalloc.is_tracing():
tracemalloc.start(25) # 保留25个帧 tracemalloc.start(25) # 保留25个帧
def _timeout_handler(self, signum, frame):
"""超时信号处理器"""
raise TimeoutException("内存分析超时")
def _run_with_timeout(self, func, *args, **kwargs): def _run_with_timeout(self, func, *args, **kwargs):
"""在超时限制下运行函数""" """
在超时限制下运行函数,使用ThreadPoolExecutor实现跨平台超时控制
"""
try: try:
# 设置信号处理器(仅在主线程中有效) future = self._executor.submit(func, *args, **kwargs)
if threading.current_thread() is threading.main_thread(): result = future.result(timeout=self._analysis_timeout)
signal.signal(signal.SIGALRM, self._timeout_handler)
signal.alarm(self._analysis_timeout)
try:
result = func(*args, **kwargs)
return result return result
finally: except FutureTimeoutError:
if threading.current_thread() is threading.main_thread(): logger.warning(f"内存分析函数 {func.__name__} 超时 ({self._analysis_timeout}秒)")
signal.alarm(0) # 取消闹钟
except TimeoutException:
logger.warning(f"内存分析函数 {func.__name__} 超时")
return None return None
except Exception as e: except Exception as e:
logger.error(f"内存分析函数 {func.__name__} 出错: {e}") logger.error(f"内存分析函数 {func.__name__} 出错: {e}")
return None return None
def __del__(self):
"""析构函数,确保线程池正确关闭"""
try:
if hasattr(self, '_executor'):
self._executor.shutdown(wait=False)
except:
pass
@eventmanager.register(EventType.ConfigChanged) @eventmanager.register(EventType.ConfigChanged)
def handle_config_changed(self, event: Event): def handle_config_changed(self, event: Event):
""" """
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""
测试修复后的超时机制
验证ThreadPoolExecutor超时机制在守护线程中的工作情况
"""
import time
import threading
import psutil
from app.helper.memory import MemoryHelper
def test_timeout_in_daemon_thread():
"""测试在守护线程中的超时机制"""
print("=== 测试守护线程中的超时机制 ===")
def long_running_task():
"""模拟长时间运行的任务"""
print("开始长时间运行的任务...")
time.sleep(60) # 模拟60秒的长时间任务
return "任务完成"
def worker():
"""守护线程工作函数"""
print(f"守护线程 {threading.current_thread().name} 开始工作")
memory_helper = MemoryHelper()
# 测试超时机制
print("测试超时机制...")
start_time = time.time()
result = memory_helper._run_with_timeout(long_running_task)
end_time = time.time()
print(f"任务执行时间: {end_time - start_time:.2f}")
print(f"任务结果: {result}")
# 测试内存分析功能
print("测试内存分析功能...")
summary = memory_helper.get_memory_summary()
print(f"内存摘要: {summary}")
# 创建守护线程
daemon_thread = threading.Thread(target=worker, daemon=True, name="TestDaemonThread")
daemon_thread.start()
# 等待线程完成或超时
daemon_thread.join(timeout=40) # 给40秒时间
if daemon_thread.is_alive():
print("守护线程仍在运行,但主线程继续执行")
else:
print("守护线程已完成")
def test_memory_analysis_performance():
"""测试内存分析性能"""
print("\n=== 测试内存分析性能 ===")
memory_helper = MemoryHelper()
# 监控CPU使用率
def monitor_cpu():
cpu_samples = []
for i in range(10):
cpu_percent = psutil.cpu_percent(interval=1)
cpu_samples.append(cpu_percent)
print(f"CPU使用率: {cpu_percent:.1f}%")
return sum(cpu_samples) / len(cpu_samples)
print("测试前CPU使用率:")
avg_cpu_before = monitor_cpu()
# 执行内存分析
print("\n执行内存分析...")
start_time = time.time()
# 测试详细内存分析(应该会超时)
analysis_file = memory_helper.create_detailed_memory_analysis()
end_time = time.time()
print(f"内存分析耗时: {end_time - start_time:.2f}")
if analysis_file:
print(f"分析报告已保存: {analysis_file}")
else:
print("内存分析超时或失败")
print("\n测试后CPU使用率:")
avg_cpu_after = monitor_cpu()
print(f"\n性能对比:")
print(f"测试前平均CPU: {avg_cpu_before:.1f}%")
print(f"测试后平均CPU: {avg_cpu_after:.1f}%")
def test_concurrent_analysis():
"""测试并发内存分析"""
print("\n=== 测试并发内存分析 ===")
def analysis_worker(worker_id):
"""分析工作线程"""
print(f"工作线程 {worker_id} 开始")
memory_helper = MemoryHelper()
# 执行内存摘要
summary = memory_helper.get_memory_summary()
print(f"工作线程 {worker_id} 内存摘要: {summary}")
# 执行垃圾回收
collected = memory_helper.force_garbage_collection()
print(f"工作线程 {worker_id} 垃圾回收: {collected} 个对象")
print(f"工作线程 {worker_id} 完成")
# 创建多个工作线程
threads = []
for i in range(3):
thread = threading.Thread(target=analysis_worker, args=(i,), daemon=True)
threads.append(thread)
thread.start()
# 等待所有线程完成
for thread in threads:
thread.join(timeout=30)
print("所有工作线程已完成")
def main():
"""主测试函数"""
print("超时机制修复测试")
print("=" * 50)
# 测试1: 守护线程中的超时机制
test_timeout_in_daemon_thread()
# 测试2: 内存分析性能
test_memory_analysis_performance()
# 测试3: 并发分析
test_concurrent_analysis()
print("\n测试完成!")
if __name__ == "__main__":
main()