Files
AgentLab/backend/app/scheduler/__init__.py
T
ThearchyHelios 45fe01effe 定时任务自动对比复核整改(后端):只比成功的运行、按发起先后挑上一次成功的基准、和两边的封存绑定,对比挪到认领之前在锁外做并限时限并发;失败通知的处理行不再重复原因
- 只比成功结束的运行,基准也只取成功的:失败、取消的运行指标不全,和上一次比只会得出「移除 N 个」,拿失败的那次当
  基准每个指标都成了「新增」。基准改成自己查同一定时任务、同一工作流、比这次先发起的运行里成功并封存的最近一次,
  不再用对比接口的 default_base(按结束时间挑:老的失败运行事后被接着跑完,会拿后一次触发当它的「上一次」)。
  中间隔着失败、取消的运行时,通知写「与上一次成功的运行(#短编号,日期)相比」,Fire.compare 多一个 previous
- 和封存绑定:运行的封存要盖住最后一条结束事件才算封存完(sealed_now)。runner 先写终态、再另起会话封存,失败后
  接着跑完的运行在两步之间还带着失败时的清单哈希,以前这一拍会直接认领、按旧封存比、通知里写旧的清单哈希;现在
  等新的封存,等不来就照常推送但不比、不写哈希。停在审批上的通知也不再写失败时留下的哈希。对比工件连同两边的清单
  哈希一起存,触发历史发现任何一边不在终态或清单哈希变了就给 null;对比途中封存变了不记(条件写)
- 时机和时限:锁里只挑出状态有变化的触发,认领挪到对比之后、锁外按条件 UPDATE 做。对比最多等 20 秒(含排队),
  同一时刻最多 2 个;超时只记警告,通知不带对比照常推送。停机时扫描在对比途中被取消,这次触发还没认领,下次照常
  比、照常推(以前先认领、后比,这条通知会永远丢掉)。同一份封存已经比过的直接用记下的结果
- 失败通知:从没开始执行的,原因刚写过,处理行不再让人「查看原因」,任务在排期上写「下一次触发会照常发起」,停用
  的写怎么重新发起;开始执行过的改成「请在 AgentLab 的「记录」中查看失败原因」,和挂起那句指向同一个导航入口
- 新增 test_scheduler_compare_guards.py(封存绑定、时限、取消、并发、复用),test_scheduler_compare.py 补真实缺
  指标的失败运行、上一次失败、补跑老运行几种用例;去掉实现逐项验证过会变红
2026-10-06 08:48:56 +08:00

52 lines
2.2 KiB
Python

"""定时任务的后台调度:到点按定时任务的设置发起工作流运行,结束、失败、停在审批时推送 Webhook。
- cron.py:五字段 cron 的解析、下次触发、中文描述
- service.py:输入模板、和工作流入口字段的对照、发起一次触发(调度循环和「立即运行」共用)
- loop.py:调度循环,条件 UPDATE 认领到点的任务
- webhook.py:通知推送(通用 / 飞书 / 企业微信 / 钉钉)
- auto_compare.py:运行成功结束后和同一定时任务上一次成功的运行自动对比,结果记在触发记录上,通知里只带计数摘要和
对比页链接
服务启动时(main.py 的 lifespan)只在 scheduler_enabled() 时调 start,关闭时总是调 stop——stop 要能在没 start
过时安全返回。start 自己也看一次开关:拷来的数据目录(检查脚本的 --stacks、验收副本)不管从哪条路径起服务,
都不会替原库把定时任务再执行一遍。
"""
from __future__ import annotations
import logging
from app.core.config import Settings, settings
logger = logging.getLogger(__name__)
def scheduler_enabled(config: Settings | None = None) -> bool:
"""调度循环开不开:AGENTLAB_SCHEDULER,默认开,0 / false / off 关(见 core/config.py)。
config 只给测试用:settings 是模块级单例,改环境变量不会让它重读。"""
return bool((config or settings).scheduler)
async def start(*, interval: float | None = None, config: Settings | None = None) -> bool:
"""起调度循环,返回起没起。开关关着时什么都不做;已经在跑时不起第二个。interval 只给测试用。"""
if not scheduler_enabled(config):
logger.info("定时任务调度已关闭(AGENTLAB_SCHEDULER),不会自动发起运行")
return False
from app.scheduler.loop import TICK_SECONDS, runner
await runner.start(interval if interval is not None else TICK_SECONDS)
return True
async def stop() -> None:
"""停调度循环,等手上的那一轮收尾。没 start 过也能安全调用。"""
from app.scheduler.loop import runner
await runner.stop()
def running() -> bool:
from app.scheduler.loop import runner
return runner.running()