feat(gateway): 接口5排队插队优先级,两级队列均支持

- 全局槽换为 _PrioritySemaphore:priority 大者排到等待队列最前,同级 FIFO,
  无等待者时才允许直接获取(收紧 asyncio.Semaphore 的 barging);
  授予槽位瞬间请求被取消时归还槽位,防泄漏
- worker 池 Condition FIFO 等待改为显式优先级等待队列,release 唤醒队首;
  被唤醒但 worker 被抢时按原序号重排;授予/取消竞态时转手唤醒下一位
- forward.py 新增 PRIORITY_PATHS(现仅接口5 /api/v1/hairline/generate)
- 新增 4 个单测:两级队列的插队 + 同级 FIFO
This commit is contained in:
xsl
2026-09-23 22:24:21 +08:00
parent 6f46fc0ccd
commit 3350e0ad77
3 changed files with 271 additions and 37 deletions
+17 -6
View File
@@ -29,6 +29,15 @@ from gateway.reqlog import extract_form_params
logger = logging.getLogger("gateway.forward")
# 插队优先级接口:排队(全局槽 / worker 池)时排到等待队列最前,同优先级 FIFO。
PRIORITY_PATHS = frozenset({
"/api/v1/hairline/generate", # 接口5:发际线PNG生成
})
def _priority_of(path: str) -> int:
return 1 if path in PRIORITY_PATHS else 0
# ---------------------------------------------------------------------------
# 全局 httpx client(连接复用)
@@ -137,7 +146,8 @@ async def proxy_request(request: Request, path: str) -> JSONResponse:
"""代理一次请求到 worker,处理重试与 base64 改写。
流程:
1. acquire worker(排队等待空闲)
0. 获取全局串行槽(PRIORITY_PATHS 中的接口插队到最前)
1. acquire worker(排队等待空闲,同样支持优先级插队)
2. 重构 multipart/form 请求,加 X-Internal-Token
3. 转发到 worker
4. 成功 → release worker → 改写 base64 → 返回 JSONResponse
@@ -161,8 +171,9 @@ async def proxy_request(request: Request, path: str) -> JSONResponse:
except Exception as ex: # noqa: BLE001
request.state.log_request_params = {"_parse_error": str(ex)}
# --- 全局串行槽(GPU 请求在此排队;超时拒绝) ---
if not await acquire_global_slot(cfg):
# --- 全局串行槽(GPU 请求在此排队;超时拒绝;优先级接口插队到最前) ---
priority = _priority_of(path)
if not await acquire_global_slot(cfg, priority):
request.state.worker_url = ""
return JSONResponse(
status_code=503,
@@ -175,13 +186,13 @@ async def proxy_request(request: Request, path: str) -> JSONResponse:
)
try:
return await _dispatch_with_retries(request, path, cfg, body)
return await _dispatch_with_retries(request, path, cfg, body, priority)
finally:
release_global_slot()
async def _dispatch_with_retries(
request: Request, path: str, cfg: dict, body: bytes
request: Request, path: str, cfg: dict, body: bytes, priority: int = 0
) -> JSONResponse:
"""在已持有全局串行槽的前提下:选 worker → 转发 → 重试 → 改写 base64 → 返回。
@@ -203,7 +214,7 @@ async def _dispatch_with_retries(
for attempt in range(attempts):
worker = None
try:
worker = await acquire_worker(cfg)
worker = await acquire_worker(cfg, priority)
# 记录当前使用的 worker,供日志中间件读取
request.state.worker_url = worker.url
except NoWorkerAvailable: