You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI Utils repeat_every在Cloud Run运行数小时后失效问题排查

问题描述

我有一个每15分钟运行一次的定时任务,代码如下:

@app.on_event("startup")
@repeat_every(seconds=60 * 15, wait_first=True)
def myFunction(db=SessionLocal()) -> None:
    test(db=db, for_test=False)

该任务初始运行正常,但运行5-6小时后便停止重复执行。查看Cloud Run日志时,发现以下错误信息:

RuntimeError: coroutine ignored GeneratorExit 

同时还有如下日志:

2022-09-29 10:04:04.263 EETTask was destroyed but it is pending!
2022-09-29 10:04:04.263 EETtask: <Task pending name='Task-850' coro=<H2Protocol.send_task() running at /usr/local/lib/python3.9/site-packages/hypercorn/protocol/h2.py:148> wait_for=<Future cancelled> cb=[_gather.<locals>._done_callback() at /usr/local/lib/python3.9/asyncio/tasks.py:767]>

必须部署新版本才能恢复定时任务运行。想了解该错误的原因、解决办法,以及是否应该改用Google Scheduler替代FastAPI Utils的定时任务。


错误原因
  • Cloud Run实例伸缩机制:Cloud Run会在实例长时间空闲、资源超限或系统维护时自动重启/回收实例。你的定时任务依赖FastAPI应用进程存活,一旦实例被终止,任务就会中断。
  • FastAPI Utils任务的进程绑定性:repeat_every装饰器创建的是应用进程内的后台任务,进程终止时,未完成的协程会被强制销毁,日志中的GeneratorExit和未完成任务提示,就是进程被终止时的典型表现。
  • 同步代码阻塞事件循环:如果test是同步函数,直接在repeat_every的任务中执行会阻塞异步事件循环,导致协程无法正常调度,长期积累可能触发进程异常终止。
解决办法

针对FastAPI Utils任务的临时修复

  • 异步兼容改造:将同步任务包装为异步执行,避免阻塞事件循环:
import asyncio
from fastapi_utils.tasks import repeat_every

@app.on_event("startup")
@repeat_every(seconds=60 * 15, wait_first=True)
async def myFunction():
    db = SessionLocal()
    try:
        # 用线程池执行同步test函数
        await asyncio.to_thread(test, db=db, for_test=False)
    finally:
        db.close()
  • 添加全局异常捕获:避免单个任务失败导致整个定时循环终止:
@app.on_event("startup")
@repeat_every(seconds=60 * 15, wait_first=True)
async def myFunction():
    try:
        db = SessionLocal()
        await asyncio.to_thread(test, db=db, for_test=False)
    except Exception as e:
        print(f"定时任务执行失败: {str(e)}")
    finally:
        db.close()
  • 配置Cloud Run最小实例数:部署时设置--min-instances=1,强制保持至少一个实例运行,避免缩容导致进程终止,但会增加持续运行的成本。

长期可靠方案:改用Google Cloud Scheduler

  • 核心优势:Cloud Scheduler是独立的托管定时任务服务,不依赖FastAPI实例存活,通过HTTP请求触发Cloud Run接口,完全规避实例伸缩带来的任务中断问题,可靠性更高。
  • 实现步骤:
    1. 在FastAPI中添加专用任务接口:
    from fastapi import Depends
    from sqlalchemy.orm import Session
    
    @app.post("/run-scheduled-task")
    def run_scheduled_task(db: Session = Depends(get_db)):
        test(db=db, for_test=False)
        return {"status": "success"}
    
    1. 在Google Cloud Console创建Scheduler任务:设置每15分钟触发一次,目标类型选HTTP,指向Cloud Run服务的/run-scheduled-task接口,配置服务账号权限确保能调用该接口。
    2. 移除原FastAPI中的repeat_every任务代码,任务逻辑完全由接口承接。
总结

如果业务对定时任务的可靠性要求高,优先改用Google Cloud Scheduler,这是最适配Cloud Run环境的方案。若暂时无法切换,可先通过异步改造、异常捕获和最小实例配置临时解决,但成本和稳定性不如专用定时任务服务。

内容的提问来源于stack exchange,提问作者muzak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 09:50:50