feat(limits): 限流器过期键周期清扫(内存上限铁律)
LoginLimiter._fails 与 rate_limit._buckets 只增不删——懒清理只覆盖被 回访的键,扫号换 IP/设备轮换产生的键永不被回访 → 进程长跑缓慢累积。 两处各加 sweep()(全表清「全部时间戳均已出窗」的键,窗沿 <= 与既有 保留判据 t > now - W 同沿),main.lifespan 起周期任务(300s loop) 启动创建/关停取消(cancel 后 await 落地,不留 pending task)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+23
-1
@@ -1,5 +1,6 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from contextlib import asynccontextmanager, suppress
|
||||
from pathlib import Path
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
|
||||
@@ -12,6 +13,8 @@ from starlette.responses import FileResponse, PlainTextResponse
|
||||
from starlette.types import Receive, Scope, Send
|
||||
|
||||
from app.engine.config import STATIC_DIR, settings
|
||||
from app.engine.services import rate_limit
|
||||
from app.platform.auth import login_limiter
|
||||
from app.platform.db import (
|
||||
Tenant,
|
||||
bootstrap_superuser,
|
||||
@@ -141,6 +144,10 @@ async def lifespan(app: FastAPI):
|
||||
registry = TenantRegistry.load_from_dir(SEED_DIR, data_dir=TENANTS_DIR)
|
||||
app.state.registry = registry
|
||||
|
||||
# 周期清扫任务句柄:启动成功(yield 前)才创建,finally 里按非 None 取消
|
||||
# ——启动半途失败时跳过,不给清理段引入未定义名。
|
||||
_sweep_task: asyncio.Task | None = None
|
||||
|
||||
try:
|
||||
await ensure_platform_schema(platform_engine)
|
||||
# 平台设置快照(Phase 4 Task 5A):schema 就绪后全表读入内存——
|
||||
@@ -196,11 +203,26 @@ async def lifespan(app: FastAPI):
|
||||
"启动清扫失败,跳过该租户(slug=%s, db=%s)",
|
||||
rt.config.slug, rt.db_path)
|
||||
|
||||
# 铁律(2026-10-19)进程内面: 登录/聊天限流器过期键周期清扫(spec
|
||||
# §7——两处内存态字典只增不删,不被回访的键永生;语义见各 sweep
|
||||
# 注记)。300s 量级足够:清速远高于键积压速度,首步 sleep 不占启动。
|
||||
async def _limiter_sweep_loop() -> None:
|
||||
while True:
|
||||
await asyncio.sleep(300)
|
||||
login_limiter.sweep()
|
||||
rate_limit.sweep()
|
||||
|
||||
_sweep_task = asyncio.create_task(_limiter_sweep_loop())
|
||||
|
||||
yield
|
||||
finally:
|
||||
# 幂等于"更少的引擎":启动半途失败也把已建好的 engine 关掉
|
||||
# (Windows 文件锁: 不关则库文件删不掉)。platform_engine 含预置的
|
||||
# 测试引擎——dispose 后 engine 对象仍可重连,测试侧重置引用即可。
|
||||
if _sweep_task is not None:
|
||||
_sweep_task.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
await _sweep_task # 等 cancel 落地:不留 pending task 给循环收尾
|
||||
await registry.dispose_all()
|
||||
await platform_engine.dispose()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user