[Bug] 大盘统计降级调用无超时,个股分析长期占用线程并阻塞后续任务

Author: bytsm54Created Sep 14, 2026Updated Sep 14, 2026

问题描述

Web 提交个股分析后,补充大盘背景所需的市场涨跌统计降级到 AkShare 新浪接口,调用长期不返回。任务持续显示 processing / 16%,占用分析线程;在 MAX_WORKERS=1 时,后续任务一直 pending。服务健康检查仍返回 200,最终只能重启服务恢复队列。

这不是 Tushare 配置未生效:本次个股日线和大盘指数行情均由 Tushare 成功返回,只有市场涨跌统计返回空后进入 fallback。

版本确认

  • 故障部署对应本地 HEAD:09fa491ae9a7c1477667dd4c4d132f9e9b3e7a9e
  • 2026-09-14 已执行 git fetch --all --prune,远端主干:1168e316269baa38752a8901331a4d7aa8b1fd07,本地落后 4 个提交。
  • 本地存在未跟踪的部署和规划文件,未执行 pull、切换分支或覆盖工作区。
  • 已核对运行容器与本地的 src/config.pydata_provider/base.pydata_provider/tushare_fetcher.py 内容哈希一致,并直接读取运行容器内的 AkShare 实现。
  • 已检查远端最新主干:base.pyakshare_fetcher.pytushare_fetcher.pytask_queue.pydaily_market_context.py 相对上述本地 HEAD 无改动。最新 #2338 改动位于 runtime scheduler 的超时通知路径,没有修改本次 Web 队列和市场统计调用路径。因此不是声称已在最新版本重新在线复现,而是确认相关实现尚未变化。

环境与配置

  • Ubuntu 宿主机,Docker,python main.py --serve-only,Web/API 异步分析。
  • Python 3.11.16;AkShare 1.18.94;requests 2.34.2。
  • TUSHARE_TOKEN 已配置且初始化成功(不提供凭据)。
  • MAX_WORKERS=1DAILY_MARKET_CONTEXT_ENABLED=true
  • 实际数据源优先级:Tushare(-1) → Efinance(0) → AkShare(1) → Pytdx(2) → Baostock(3) → Yfinance(4) → Tencent(5)。

实际发生过程与日志

以下均为北京时间,日志摘录已去掉客户端地址、凭据和无关信息:

2026-09-13 21:25:13  605168.SH(三人行)任务提交
2026-09-13 21:43:20  开始处理 605168.SH
2026-09-13 21:43:21  Tushare 日线成功,43 行,耗时 1.47s;保存成功
2026-09-13 21:43:21  MarketReview action=start trigger_source=daily_market_context region=cn
2026-09-13 21:43:27  Tushare 获取指数行情成功,count=6
2026-09-13 21:43:29  MarketStats action=provider_empty provider=TushareFetcher elapsed=1.78s
2026-09-13 21:43:32  Efinance get_realtime_quotes failed: Expecting value: line 1 column 1 (char 0)
2026-09-13 21:43:49  AkShare stock_zh_a_spot_em failed: RemoteDisconnected; fallback=ak.stock_zh_a_spot
2026-09-13 21:43:54  MarketStats provider=AkshareFetcher api=ak.stock_zh_a_spot action=request_start
2026-09-14 12:45:09  603259.SH(药明康德)任务提交
2026-09-14 15:56     查询实时任务列表:605168.SH processing / 16%;603259.SH pending / 0%

截至处置时,该新浪接口没有 request_complete / failed 日志,持续约 18 小时 12 分钟。数据库中没有 605168 的报告。任务列表返回的状态也证实后台未结束,不能仅归因于前端进度未刷新。

复现条件

  1. 启动 Web/API 服务,设置上述配置,在需要生成大盘背景时提交个股分析。
  2. 市场统计链路发生 Tushare 空结果、Efinance 失败、AkShare 东财失败,进入 AkShare 新浪 fallback。
  3. 新浪请求连接后长期不返回,再提交第二个股票任务。
  4. 观察第一个任务长期 processing,第二个任务 pending,健康检查仍成功。

这是实际生产事件,外部网络阻塞不保证每次触发。回归验证应使用受控的慢响应/不返回服务构造反例,不依靠重复请求真实供应商碰运气。

根因与责任边界

  • AkshareFetcher.get_market_stats 直接同步调用 ak.stock_zh_a_spot_em()ak.stock_zh_a_spot(),没有为该调用设置明确期限。
  • 实际安装的 AkShare 新浪实现按页执行 requests.get(zh_sina_stock_url, params=...),未传 timeout
  • DataFetcherManager.get_market_stats 只有返回空/抛异常才会转下一个源,不能处理调用一直不返回。
  • AnalysisTaskQueue 同步占用执行线程。当前存在 cancelled 状态枚举,但未找到能实际中止该分析的取消接口。
  • Tushare 返回空的具体原因尚未查明,不能据此认定 Token、积分不足或供应商故障。该诊断缺口应记录,但主问题是 fallback 阻塞时缺少有界结束与队列恢复能力。

已搜索相关 Issue;#2328 标题涉及分析超时,但可读取的正文缺少版本、日志及复现信息,尚不能确认是同一根因。

期望行为与验收条件

  • 市场统计的东财/新浪等外部请求及降级链有明确时间边界;某个源不返回不会无限占用分析线程。
  • 受控复现中,MAX_WORKERS=1 下首任务遇到阻塞后,后续任务仍能在有界时间内开始执行,无需重启服务。
  • 不能仅把 UI 状态改成 failed/cancelled 或对 Future 做软超时,就声称已释放资源;验证实际队列容量恢复,重复超时不持续积累后台请求/线程。
  • 超时后的失败或降级行为沿用现有业务契约,日志与任务状态准确说明数据缺失和超时来源,不伪造成功或市场统计数据。
  • 保留 Tushare 优先与合法 fallback 行为,覆盖首选成功、首选空结果、后备异常、后备长期不返回;至少通过一次 Web/API 最终入口验证。
  • 排查 Tushare 市场统计空结果的路径,补足必要诊断;证据不足时明确未知,不用禁用其他数据源或扩大并发来掩盖阻塞。

建议优先级 P1:已实际阻塞后续分析,需要服务重启才能恢复。修复重点是请求期限、实际资源释放与队列可继续执行,不要求为本 Issue 重构整个队列系统。

已采取的临时恢复与验证边界

2026-09-14 15:56 经用户要求停止故障任务,重启分析服务。内存队列随重启清空,已恢复唯一排队任务 603259.SH,保留其 detailed 报告类型、auto 分析阶段及 hot_theme 策略;本次恢复未发送外部通知。

重启后健康检查成功;15:57:14 药明康德的大盘统计由 Tushare 在 2.12s 内返回,已通过此前的阻塞步骤。605168 未重新提交,已有历史报告保留。此次未修改代码,尚未修复超时缺陷,也未开展回归测试。

后续修复需同步相关专题文档与 CHANGELOG。回滚应可撤销补丁及新增配置、保留报告数据;重启仅为临时恢复,内存中的排队任务需要先记录再恢复。

Source: ZhuLinsen/daily_stock_analysis