fix: keep references to lifespan background tasks (#28053)

periodic_usage_pool_cleanup, periodic_session_pool_cleanup and
scheduler_worker_loop were started with asyncio.create_task and their
handles discarded. The event loop keeps only a weak reference to a task,
so a task with no other referent can be garbage collected while it is
suspended at an await. All three are while True loops meant to run for
the process lifetime, and if one is collected the failure is silent:
pool entries stop being cleaned up, or automations and calendar alerts
stop firing, with nothing logged.

Six lines above, redis_task_command_listener is already stored on
app.state and cancelled on shutdown. This applies the same treatment to
the other three.

Closes #28052
This commit is contained in:
Lin Junrong
2026-08-17 15:18:22 +08:00
committed by GitHub
parent 0007369f4e
commit 211906d799
+7 -3
View File
@@ -382,12 +382,12 @@ async def lifespan(app: FastAPI):
limiter = anyio.to_thread.current_default_thread_limiter()
limiter.total_tokens = THREAD_POOL_SIZE
asyncio.create_task(periodic_usage_pool_cleanup())
asyncio.create_task(periodic_session_pool_cleanup())
app.state.periodic_usage_pool_cleanup = asyncio.create_task(periodic_usage_pool_cleanup())
app.state.periodic_session_pool_cleanup = asyncio.create_task(periodic_session_pool_cleanup())
from open_webui.utils.automations import scheduler_worker_loop
asyncio.create_task(scheduler_worker_loop(app))
app.state.scheduler_worker_loop = asyncio.create_task(scheduler_worker_loop(app))
if await Config.get('models.base_models_cache'):
try:
@@ -468,6 +468,10 @@ async def lifespan(app: FastAPI):
if hasattr(app.state, 'redis_task_command_listener'):
app.state.redis_task_command_listener.cancel()
app.state.periodic_usage_pool_cleanup.cancel()
app.state.periodic_session_pool_cleanup.cancel()
app.state.scheduler_worker_loop.cancel()
await publish_event(app, EVENTS.SYSTEM_SHUTDOWN_COMPLETED, source='system')