Files
vrsub/src/wov_app/main.py
T
cat-shark c063aed1f1 fix: 批量任务残留PENDING仍误标COMPLETED——置完成前校验无残留+崩溃恢复批量任务
事故(batch_969fabe74b83 等 3 个任务实测):批量引擎串行处理到 9.9GB
大视频时中断,_run_job 无条件收尾把任务置 COMPLETED,留下"N 个 PENDING
待处理却已完成"的僵尸状态——3 个任务遗留的 PENDING 完全相同(kiwvr-887/
mdvr-413/savr-1077 共 10 个大视频),且均卡在大文件前从未被真正处理。

- batch.py: _run_job 置 COMPLETED 前校验全部非 SKIPPED 视频已结束
  (无 PENDING/PAUSED 残留),否则保持 RUNNING 交引擎下一轮续跑
- db.py/main.py: 新增 recover_interrupted_batch_jobs,重启时把 RUNNING
  批量任务恢复 QUEUED(否则停在 RUNNING 永远不会被再次拾起)
- tests: 新增 2 条 TDD 回归(残留 PENDING 不置 COMPLETED、批量崩溃恢复)
- scripts/fix_zombie_batch_jobs.py: 历史僵尸数据修复脚本(置回 QUEUED 续跑)
- AGENTS.md: 补充完成任务判定与崩溃恢复约定
2026-09-07 11:25:33 +08:00

99 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""FastAPI 应用入口。
负责组装数据库、进程内节点注册表、调度器与静态前端,并在应用生命周期内
管理后台线程的启动与清理。单体版所有节点在同一进程内直接调用。
"""
from __future__ import annotations
# 先加载 .env(含 LLM API Key 等本地配置),再导入读取环境变量的 config。
from dotenv import load_dotenv
load_dotenv()
import os # noqa: E402
from contextlib import asynccontextmanager # noqa: E402
from datetime import datetime, timezone # noqa: E402
from pathlib import Path # noqa: E402
from fastapi import FastAPI # noqa: E402
from fastapi.middleware.cors import CORSMiddleware # noqa: E402
from fastapi.staticfiles import StaticFiles # noqa: E402
from wov_app import registry
from wov_app.batch import BatchWorker
from wov_app.config import BATCH_ENABLED, DB_PATH, STORAGE_DIR, WORKSPACE_ROOT
from wov_app.db import Database
from wov_app.maintenance import OrphanCleaner
from wov_app.routers import apps, batch, workflows
from wov_app.scheduler import WorkflowScheduler
from wov_app.seed import seed_default_workflows
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期:启动时初始化数据、注册节点和后台服务,退出时回收资源。"""
# 确保数据与存储目录存在,避免首次启动写文件失败。
db = Database(DB_PATH)
STORAGE_DIR.mkdir(parents=True, exist_ok=True)
# 静态注册全部内置节点到进程内注册表(内存态,不落库)。
registry.register_all()
# 默认创建演示工作流,可关闭便于测试。
if os.getenv("WOV_AUTO_SEED", "1") == "1":
seed_default_workflows(db)
# 重启恢复:上次进程被杀时遗留的 RUNNING 任务恢复为 QUEUED
# 调度器会从产物表断点续跑(不重复已完成节点);PAUSED 保持等待显式 resume。
# 批量任务同样恢复:RUNNING 的批量 job 置回 QUEUED,其关联 run 由
# recover_interrupted_runs 恢复,批量引擎重新拾起后从断点续跑剩余 PENDING
# (不恢复则批量任务停在 RUNNING 永远不会被再次驱动,未处理视频永久残留)。
db.recover_interrupted_runs(datetime.now(timezone.utc).isoformat())
db.recover_interrupted_batch_jobs(datetime.now(timezone.utc).isoformat())
scheduler = WorkflowScheduler(db, STORAGE_DIR)
# 调度器默认开启,处理排队中的任务;测试可关闭后手动执行。
if os.getenv("WOV_SCHEDULER_ENABLED", "1") == "1":
scheduler.start()
cleaner = OrphanCleaner(db, STORAGE_DIR)
# 孤儿数据清理默认开启,定时清除死数据;测试可关闭。
if os.getenv("WOV_CLEANUP_ENABLED", "1") == "1":
cleaner.start()
# 批量处理引擎:处理用户所选文件夹的批量任务(source=batch 的运行),
# 与主调度器互不抢占;默认开启,测试可关闭。
batch_worker = BatchWorker(db)
if os.getenv("WOV_BATCH_ENABLED", "1") == "1":
batch_worker.start()
# 共享对象挂到 app.state,路由通过 Depends 延迟获取。
app.state.db = db
app.state.scheduler = scheduler
app.state.cleaner = cleaner
app.state.batch = batch_worker
yield
# 退出时先停批量引擎、调度器与清理器,避免残留后台线程。
batch_worker.stop()
cleaner.stop()
scheduler.stop()
app = FastAPI(title="VRSub API(单体版)", version="0.1.0", lifespan=lifespan)
# MVP 阶段不做鉴权,允许跨域便于本地调试与静态页面访问。
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(workflows.router)
app.include_router(apps.router)
app.include_router(batch.router)
@app.get("/health")
def health() -> dict:
"""进程存活探针,供部署环境与前端检测后端可用性。"""
return {"status": "ok", "service": "wov-api", "mode": "monolith"}
# 静态前端目录位于仓库根下的 web,由 FastAPI 直接挂载。
FRONTEND_DIR = WORKSPACE_ROOT / "web"
app.mount("/", StaticFiles(directory=FRONTEND_DIR, html=True), name="frontend")