Parent: #6(生命周期审计发现,先登记再修)
症状
三个 create_task 调用点既不保存引用、无异常收集、也不受停机管理:
app/scheduler.py:1204 — 启动首次 _rescan_sources():主体 DB 查询/自愈 UPDATE 无 try 包裹,启动期 DB 故障即静默死亡(仅 GC 时 "Task exception was never retrieved");停机不等它,与 main.py:443 engine.dispose() 竞争 session。
app/scheduler.py:1205 — _recover_analysis_jobs_on_startup():函数体(109-114)零 try/except,异常静默丢失。
app/api/v1/daily_reports.py:294 — 日报后台 _bg_generate:mark_error 失败被裸 except Exception: pass(291-292)吞掉 → 报告永远卡在 GENERATING;停机无取消,且无 DB 持久化 dispatcher 兜底。
期望 vs 实际
#6 验收:「No unowned create_task path can silently lose exceptions or outlive shutdown without an explicit lifecycle policy」。兴趣向量重建(_rebuild_tasks + done callback + drain)是仓内正确范例,应向其对齐。
边界
scheduler 启动路径 / daily_reports 后台生成 / lifespan 停机序。
严重度 / 复现性
P1 / 启动期 DB 抖动或日报生成失败时必现。
回归测试候选
异常捕获记日志、任务注册可 drain、日报 mark_error 失败不吞(报告状态落 ERROR)。
日志/证据
审计第一路报告(见 #6)。
Parent: #6(生命周期审计发现,先登记再修)
症状
三个
create_task调用点既不保存引用、无异常收集、也不受停机管理:app/scheduler.py:1204— 启动首次_rescan_sources():主体 DB 查询/自愈 UPDATE 无 try 包裹,启动期 DB 故障即静默死亡(仅 GC 时 "Task exception was never retrieved");停机不等它,与 main.py:443engine.dispose()竞争 session。app/scheduler.py:1205—_recover_analysis_jobs_on_startup():函数体(109-114)零 try/except,异常静默丢失。app/api/v1/daily_reports.py:294— 日报后台_bg_generate:mark_error失败被裸except Exception: pass(291-292)吞掉 → 报告永远卡在 GENERATING;停机无取消,且无 DB 持久化 dispatcher 兜底。期望 vs 实际
#6 验收:「No unowned create_task path can silently lose exceptions or outlive shutdown without an explicit lifecycle policy」。兴趣向量重建(
_rebuild_tasks+ done callback + drain)是仓内正确范例,应向其对齐。边界
scheduler 启动路径 / daily_reports 后台生成 / lifespan 停机序。
严重度 / 复现性
P1 / 启动期 DB 抖动或日报生成失败时必现。
回归测试候选
异常捕获记日志、任务注册可 drain、日报 mark_error 失败不吞(报告状态落 ERROR)。
日志/证据
审计第一路报告(见 #6)。