数据自愈超时机制失效分析claude 2

数据自愈超时机制失效分析

日期:2026-02-23
问题HEALING_TIMEOUT_SECONDS=300 无效,服务启动时数据自愈运行 ~19 分钟(498 对配对)


一、现象

09:20:16 - 数据自愈启动 | 498 个配对 | timeout=300s
09:25:16 - app - ERROR - 数据库连接错误: 数据自愈超时 (300秒)   ← 超时触发了,但...
09:25:16 - orchestrator - ERROR - 加载历史数据失败 - 数据库错误: 数据自愈超时
09:39:30 - 数据自愈仍在继续处理第 340+ 对                       ← 19分钟后还未停止

预期:300s 后打印 "数据自愈超时,已完成 N 个 symbol" 并退出。
实际:报错一次后继续运行所有剩余配对。


二、机制原理

healing_timeout 使用 Unix signal.alarm()

# orchestrator.py:76
@contextmanager
def healing_timeout(seconds: int):
    sigalrm = getattr(signal, "SIGALRM", None)
    if sigalrm is not None:
        def timeout_handler(signum, frame):
            raise TimeoutError(f"数据自愈超时 ({seconds}秒)")
        signal.signal(sigalrm, timeout_handler)
        signal.alarm(seconds)   # 一次性闹钟,只触发一次
        try:
            yield
        finally:
            signal.alarm(0)

关键约束signal.alarm()一次性的——超时只触发一次 TimeoutError。如果这次 TimeoutError 被内层代码吞掉,后续代码永远不会再超时。


三、根本原因

Python 内置 TimeoutError 的继承链:

TimeoutError → OSError → Exception → BaseException

TimeoutErrorException 的子类,因此所有 except Exception 都会捕获它。

被吞掉的完整调用链

healing_timeout(300) 触发 → TimeoutError 抛出
         ↓
_load_zscore_history (orchestrator.py:511)
    except Exception as e:                         ← ① 捕获 TimeoutError
        logger.error("加载历史数据失败 - 数据库错误")
        raise RuntimeError("加载历史数据失败: ...") from e   ← TimeoutError 被包装为 RuntimeError
         ↓
heal_and_prepare (orchestrator.py:212)
    except RuntimeError as e:                      ← ② 捕获包装后的 RuntimeError
        logger.error("数据自愈失败(数据库/SQL错误)")
        return HealingResult(status='failed')      ← 正常返回,不抛出异常
         ↓
_run_data_healing for 循环继续下一对配对            ← ③ TimeoutError 从未到达外层

外层 except TimeoutError (line 1863)               ← 永远不会执行

备用触发路径(TimeoutError 在其他位置触发时)

位置 方法 行为
repair_executor.py:183 _repair_from_klines except Exceptionreturn 0
repair_executor.py:299 _extract_kline_window except Exceptionreturn None
repair_executor.py:345 _compute_zscore except Exceptionreturn None

以上任意一处捕获后,signal.alarm 的一次性触发机会耗尽,剩余配对永远不会超时。


四、影响

指标 预期 实际
数据自愈耗时 ≤300s ~1140s(19 分钟)
服务启动延迟 ~5 分钟 ~20 分钟
超时日志 300s 后打印已完成数量 打印一次错误后继续
WebSocket 启动 ~5 分钟后 ~20 分钟后

五、修复方案

原则:让系统级中断(TimeoutErrorKeyboardInterrupt)穿透所有内层 except Exception,不增加新抽象。

修复模式(在每个受影响的 except Exception 之前添加):

except (TimeoutError, KeyboardInterrupt):
    raise   # 直接穿透,不处理
except Exception as e:
    # 原有逻辑不变
    ...

需修改的位置(共 5 处)

src/utils/data_healing/orchestrator.py

# 行 511 — _load_zscore_history
# 修改前
except Exception as e:
    logger.error(f"加载历史数据失败 - 数据库错误: {e}", exc_info=True)
    raise RuntimeError(f"加载历史数据失败: {e}") from e

# 修改后
except (TimeoutError, KeyboardInterrupt):
    raise
except Exception as e:
    logger.error(f"加载历史数据失败 - 数据库错误: {e}", exc_info=True)
    raise RuntimeError(f"加载历史数据失败: {e}") from e

src/utils/data_healing/repair_executor.py

# 行 183 — _repair_from_klines
except (TimeoutError, KeyboardInterrupt):
    raise
except Exception as e:
    logger.error(f"Level 1修复失败: {e}", exc_info=True)
    return 0

# 行 299 — _extract_kline_window
except (TimeoutError, KeyboardInterrupt):
    raise
except Exception as e:
    logger.debug(f"提取窗口失败: {e}")
    return None

# 行 345 — _compute_zscore
except (TimeoutError, KeyboardInterrupt):
    raise
except Exception as e:
    logger.debug(f"zscore计算失败: {e}")
    return None

src/services/realtime_kline_service_base.py

# 行 1860 — _run_data_healing for 循环
except (TimeoutError, KeyboardInterrupt):
    raise
except Exception as e:
    failed += 1
    self.logger.warning(f"自愈异常: {symbol} | {e}")

六、修复后行为

09:20:16 - 数据自愈启动 | 498 个配对 | timeout=300s
...(处理约 150-200 对)...
09:25:16 - ⚠️ 数据自愈超时 (300s),已完成 N 个 symbol   ← 正确退出
09:25:17 - 订阅数量: 行情=687 交易=3
09:25:17 - ✅ 实时K线分析服务初始化完成
09:25:20 - 增强型WebSocket管理器初始化完成              ← WebSocket 正常启动

服务启动时间从 ~20 分钟缩短至 ~5 分钟。


七、注意事项

  • signal.alarm 仅 Unix/Linux/macOS 有效,Windows 下 SIGALRM=None,无超时保护(现有行为不变)
  • repair_executor.py:370_insert_records)的 except Exception 末尾已有 raise,无需修改
  • 修复不影响正常自愈流程,仅改变异常穿透路径

Read more

数据自愈模块提速3

数据自愈并发优化设计方案 一、现状分析 当前流程 _run_data_healing(realtime_kline_service_base.py:1807): for (symbol, base_symbol) in heal_pairs: # ~50+ 配对,串行 DataHealingOrchestrator(...) # → RepairExecutor.__init__ │ # → KlineDataFiller.__init__ │ # → Info(MAINNET_API_URL) HTTP 握手 ~0.85s │ # ← 第 2-50 个握手完成后才被 shared_executor 覆盖,全部浪费! _load_zscore_history()

By SHI XIAOLONG

数据自愈模块提速2

数据自愈并发优化设计方案 一、现状分析 当前流程 _run_data_healing(realtime_kline_service_base.py:1807): for (symbol, base_symbol) in heal_pairs: # ~50+ 配对,串行 DataHealingOrchestrator(...) # → RepairExecutor.__init__ │ # → KlineDataFiller.__init__ │ # → Info(MAINNET_API_URL) HTTP 握手 ~0.85s │ # ← 第 2-50 个握手完成后才被 shared_executor 覆盖,全部浪费! _load_zscore_history()

By SHI XIAOLONG

数据自愈模块综合问题分析报告2

数据自愈模块优化设计文档 版本:v1.0 日期:2026-02-23 范围:src/utils/data_healing/ + src/services/realtime_kline_service_base.py(_run_data_healing 部分) 目录 1. 问题全景与优先级总表 2. BUG-01:加载语义与修复语义不一致 3. BUG-02/03:单条/少量记录无法生成修复目标 4. BUG-04:日志误导性 5. BUG-05:重复写入(BUG-01 副作用) 6. BUG-06:test_basic.py 与 Diagnosis 定义失同步 7.

By SHI XIAOLONG