任务执行与结果发布
约 3822 字大约 13 分钟
Web 长耗时操作由组合入口 app/web/services/background_jobs.py 注册领域处理器。任务管理器位于 app/jobs/manager.py,只负责登记、调度、取消和恢复;持久化位于 app/db/job_store.py。公开记录与执行代次契约位于 app/jobs/contracts.py,领域处理器通过 JobContext 报告进度及提交结果。
登记与状态
去重键以用户和操作类型为作用域。登记在 SQLite 写事务中检查活动记录;相同键、不同请求会被拒绝,不会返回其他用户的任务。重复请求仍可获得已经登记的同一操作。
每次执行携带不可变的作业 ID 和 attempt。进度、结果和失败写入都校验当前代次;旧执行不能覆盖重试后的状态。已成功的结果不能改为取消,终态之后的进度也不能写回。
安全作业在异常重启后以新代次恢复;已请求取消的作业结算为取消。中断的上传保留人工核对提示,不自动重放外部操作。手动重试须等待上一次执行退出。
本地结果的提交点
领域先完成耗时计算,再进入 JobContext.result_transaction()。该事务在首次读取前取得写锁,验证作业仍在当前 running 代次;领域在同一 Session 写入正式结果,最后调用 context.complete(db, result)。退出时缺少成功终态会回滚整个事务。
- 本地录播导入:场次、分段、弹幕、流水线任务、导入结果和作业成功共同提交。
- 人工出片与审核重渲染:先准备主视频、派生版本及文案草稿,再复验候选、审核输入和流水线租约;成片引用、候选状态、关联流水线状态与作业成功共同提交。
- 合集:复验主题、事件关联、审核输入及来源成片后,共同发布合集版本、主题状态、候选状态与作业成功。
取消请求和成功发布使用同一数据库写锁。取消先提交则后续发布被拒绝;成功先提交则取消返回操作已经结束。人工审批本身是已经确认的输入行为,取消媒体计算不会撤销审批记录。
渲染文件使用每次执行独占的名称,重试不会覆盖已有版本。草稿没有正式数据库引用;计算失败清理本次声明的输出,发布失败先检查正式引用再清理。文件系统和 SQLite 不具备共同事务,进程在文件生成后突然退出仍可能留下无引用文件;这类文件不作为成功结果,不能据此重发上传。
上传等外部效果不具有本地事务原子性。远端状态由上传领域核对,任务成功只引用已经确认的结果;中断和未知结果保持明确诊断。
执行资源与应用启停
app/core/execution.py 将同步执行与异步等待分开。取消等待只传递取消意图;真实线程退出前继续占用并发名额。有限数据库领取操作在取消后收尾,并撤回尚未派发的结果。媒体进程及 ASR IPC 清理失败时保留执行和资源占用,记录错误并重试专属资源的清理;关闭超时会报告失败,不能作为成功退出或允许替代执行的依据。
流水线预算按任务与租约代次归还。同步业务及其专属子进程完成收尾后,才撤销执行登记和结算取消。ASR 失败槽进入隔离清理状态,角色额度在进程、IPC 线程均退出后归还;尚有解码操作的监管器不能被启动流程替换。Windows 使用 Job 对象确认活动进程数归零;POSIX 发出终止信号后检查独占进程组已无活动成员,不重复向已终止的进程组发信号。
app/core/supervisor.py 只接收具名服务的启动、停止接口,不导入领域或 Web 代码。组合入口先登记清理再启动服务;部分启动失败也会先通知停止接单,再逆序执行其余清理。任一清理失败必须向服务器报告,不能声明整个应用已安全关闭。
热点采集保留正在执行的同步模型调用,关闭超时不允许重启第二个采集循环。通知投递在发送线程完成后才撤销执行器引用,关闭等待被取消时也不能重新创建执行器。系统权限持续阻止资源回收时,执行保持占用并输出明确诊断;恢复权限后才结束清理。
录制预约的持久规则位于 app/recording/schedules.py,执行循环位于 app/recording/schedule_runner.py。Web 仅提供录制和通知适配;预约数据库操作在独立线程执行,避免阻塞 HTTP 事件循环。既有预约 CRUD 接口保留。
配置保存与生效
app/core/configuration_policy.py 是生效规则的共同来源。保存服务与配置代理使用同一策略,设置中心区分立即读取、下一安全边界和重启进程三类行为。普通任务、录制和嵌套调用保留各自的配置快照;独立任务入口获取新快照。通知总开关与盲审开关在下一次读取时使用新值,已经发送的外部请求不撤销。
资源容量、模型保留策略及调度间隔在下一次申请、空闲检查或调度时读取新值,降低上限不会强制终止已有执行。审核租约期限在下一次领取时生效,关闭期限在下一次发起停止时确定。新建房间使用当前默认阈值,已有房间仍使用自己的设置;分析缺少房间设置时,使用本次任务快照中的默认值。
ASR 启动预加载、启动恢复回溯窗口在进程首次加载配置时确定;保存新值会标为等待重启,同一进程重新加载配置仍保留原值。数据库路径、存储路径和认证等部署配置继续只读,不由网页隐式迁移。
管理接口分别返回 saved、effective、runtime_revision、pending_restart 和仍保留旧值的任务/录制快照数量。effective 表示当前服务提供给新运行单元的值,已有快照另行列出;这些数量是查询时的观察结果。凭据仅返回是否已设置,任何状态字段均不回显原值。另一个进程保存时,当前服务在下一个配置刷新边界读取新版本,界面明确显示尚未读取的状态。
网页、CLI 上传开关和登录 Cookie 写入共同使用配置验证与事务。整批保存仅递增一次版本;提交失败不发布内存视图、重启意图或部分配置。已注册配置与其他业务存储键不能混合写入。插件及模型配置保持各自的领域验证入口。
模型连接与业务路由
app/models/contracts.py 定义共享连接和业务路由。连接只保存地址、凭据与启用状态;路由分别为高光分析、转写整理、热点、整场总结、文案、远程 ASR 选择模型。同一连接的文本路由复用 HTTP 客户端;凭据轮换后旧客户端等活动请求退出才关闭。路由输出上限与业务预算取较小值,文本调用及测试均保留每日预算检查。
共享预算账本在跨进程文件锁内读取、累加和原子持久化。Windows 文件替换遇到 WinError 5/32/33 时最多尝试 7 次,锁在恢复期间持续持有,单次增量不会重复累加;其他错误立即失败。记账或清理失败均停止模型调用,不重新发送收费请求,也不继续另一条收费路由。
GET/PUT /api/model-configuration 读取脱敏目录并按统一 revision 保存。密钥留空保留原值,清空需明确声明;修改地址时必须重新提供密钥或清空。凭据合并、重复 ID、连接引用、已选择 ASR 路由和版本校验均在同一写事务内完成,失败不发布内存状态。已有任务保留完整目录快照,新任务才使用新路由。
旧 llm_providers 和热点专用配置在未保存新目录时继续生效;页面提供完整转换预览,不会自动持久化。保存新目录时原通用模型分别用于五项文本业务,热点专用模型保留原优先顺序,并移除旧通用配置的重复凭据。新目录启用后旧整表写入被拒绝,防止覆盖其他业务路由。独立 ASR 环境配置继续作为未选择路由时的显式来源。
远程 ASR 使用 ASR_REMOTE_ROUTE_ID 指向已保存且启用的 ASR 路由;仅主引擎明确选择 openai_compatible 时才上传音频。父进程解析任务快照,只向模型子进程传递选中连接的地址、模型与凭据,并清除路由引用,子进程不访问目录数据库或其他 LLM 密钥。本地 CUDA、ASR 资源监管及 Whisper 回退规则保持原有行为。
POST /api/model-configuration/test 只调用管理员选中的文本路由,不保存草稿;远程 ASR 必须用用户明确提交的短录播验证,连通测试不会自动上传音频。网页草稿仅保存在当前页面内存,退出登录时清理;版本冲突时保留原草稿和版本,需在最新配置上重新修改。
提交后通知与状态恢复
app/db/notifications.py 在 Session flush 前收集查询范围,在最外层提交成功后交给 app/core/notifications.py。保存点提交只合并提示,保存点或整个事务回滚均丢弃对应提示。通知不读取尚未提交的业务结果,不携带文件路径、模型凭据、作业内容或日志正文;领域执行不等待浏览器。
GET /api/events 使用现有 Basic 或 Docker 会话认证,拒绝跨站请求和查询参数,凭据不能放在 URL。管理员接收状态提示,审核员只接收审核数据和本人作业提示。每个身份具有独立的随机游标前缀和递增序号,其他身份的活动不改变其游标。每个进程最多保留 64 个身份、每个身份 128 条提示;活跃连接最多 64 个、每个身份 4 个,超限返回 429 和 Retry-After。空闲身份可被淘汰,重启、过期或跨身份游标均返回 resync。
流每秒复核授权,Docker 检查不延长空闲期限;退出、密码重置或审核员移除后结束流。每 10 秒发送心跳,单次发送超过 10 秒终止并释放连接登记。独立前端代理逐块读取并刷新,Nginx 保持关闭代理缓冲。
正式 CLI 后端(含 Docker、Portable 及开发重载)将 Uvicorn 的 HTTP 关闭宽限期设为 10 秒,避免长连接阻止进入 lifespan 清理。HTTP 收尾后,Supervisor 继续按各领域规则等待线程、媒体进程及 ASR 物理退出;这不是媒体任务的强制终止期限。自行运行 Uvicorn 时需加 --timeout-graceful-shutdown 10。
通知历史仅存在于当前进程;其他进程、直接 SQL 以及非数据库运行状态通过每 30 秒的固定 resync 和 HTTP 查询恢复,不依赖跨进程内存广播。数据库继续作为权威来源,此通知协议不构成持久事件日志或任务成功回执。
前端只有一个订阅入口,使用成熟 SSE 解析器和同源 fetch,Basic 凭据及 Last-Event-ID 只放请求头。解析缓存限制为 64 KiB;重复 invalidate 去重、未知格式重新查询、断线有限退避、429 遵守 Retry-After、25 秒无数据主动重连。正常通知合并后失效当前查询,在线时轮询间隔至少 30 秒;连接不可用时恢复原有轮询。隐藏页面不因通知反复请求数据,重新可见时查询;账号切换或退出取消连接,草稿与失败写操作不被通知覆盖或重放。
通知刷新与轮询共用错误恢复策略:已经返回 4xx 的查询继续等待用户显式重试,重连或重新显示页面不绕过这一限制;例如失效的二维码任务不会被通知反复查询或重新创建。
数据库格式与应用版本
schema_meta.schema_version 与模型指纹共同声明数据库格式,当前格式仍为 6。启动继续检查实际表列、关键唯一约束、外键及完整性;schema_meta.app_version 只记录创建来源,不参与应用版本相等判断,复用时也不补写。相同完整格式可跨应用版本启动,版本升级本身不创建新库。
数据库日志仅在格式校验和配置加载均成功后启用。初始化开始时撤销旧日志代次;失败诊断仍写入文件和控制台,不修改被拒绝的数据库。队列记录绑定产生时的数据库代次,重新初始化或更换引擎后不会将旧记录写入新目标。
数据库路径继续来自 DATABASE_URL。Portable 以安装根目录作为子进程工作目录,版本化源码独立存放;Docker 保留 /data/blc.db,源码部署的相对路径按当前工作目录解析。不会扫描其他目录寻找数据库或静默改用空库。启动遇到不兼容格式时保留原文件并明确失败,不自动迁移、删除或覆盖;升级先停机备份,在独立副本验证数据与关联媒体。
格式兼容不承诺旧程序理解所有新配置或业务元数据,降级仍需核查目标版本,必要时使用升级前备份。测试通过真实 init_db() 入口验证跨应用版本、重复启动和拒绝不兼容格式,逐项比对完整 SQL 内容及创建来源,避免仅验证辅助函数。
架构检查
python scripts/check_architecture.py 检查已经声明的任务层依赖方向,禁止任务管理器、任务契约及任务存储反向依赖 Web 或具体领域。检查器本身有反例测试,覆盖局部导入、相对导入和缺失模块。
导入检查不代替事务、线程或进程行为验证。tests/integration/test_job_transactions.py 使用真实 SQLite 连接验证发布与取消竞争、原子回滚、代次隔离和并发去重;媒体集成测试继续验证真实导入、字幕、派生版本及审核调用链。