fix(backend): 补齐单次上传端点的并发护栏(BACKLOG C7)
- middleware/concurrency_guard.js: HEAVY_PATTERNS 增加单段上传正则
/^\/api\/upload\/[^/]+$/i —— 单段锚定([^/]+$)保证不误伤"两段"的轻路由:
GET /status、GET /submissions、DELETE /:id(放宽成前缀匹配会让「查状态也 429」)
- controller/upload.js: upload() 补 await acquireFileIo() +
finally { if (ioAcquired) releaseFileIo(); }
⚠️ 不用 uploadChunk 的无条件 release:本方法 429 分支是 acquire 失败后 return,
无条件释放会把"别人的"配额放掉 => 计数下溢 => 护栏静默失效
背景:修复前该端点是**唯一在用**的作品上传路径(Competition.vue:477),却既不在
concurrencyGuard.HEAVY_PATTERNS(表内 chunk/merge 已成死路由)、也不调 file_io_guard
=> 零护栏。因服务器入带宽仅 ≈1.25MB/s 天然节流,当前风险有限,但按 C3 升入带宽
后即变实质风险 => 与 C3 绑定,先补护栏。
验证:
- verify-guard-r43.mjs 33/33(单元:路径判别力 + 满载 429 + 配额隔离 + 释放语义 + 变异自证)
- verify-guard-r43-live.mjs 8/9(真接口:30 路 5MB 并发上传 => 20 成功 / 10 个 429 +
Retry-After: 2;15 个轻路由探针在并发中全 200;唯一失败为清理步骤,根因是
saveSubmissionRecord 的 upsert 语义,非缺陷)
- 重启后 master PID 23684;[AppBoot][pool] 40 <= 151
This commit is contained in:
@@ -18,7 +18,29 @@
|
||||
// file_io_guard 限制「同时进行的文件流数(按流生命周期释放)」(防 fd/事件循环耗尽)。
|
||||
// 双保险,使单 worker 在极端并发下「降级(429)而非崩溃」。
|
||||
|
||||
// 🔴 单次流式上传(2026-09-29 · R43 补齐,原为**零护栏**)
|
||||
//
|
||||
// 背景:R42 排查 C5(作品上传错峰)时发现 —— **在用的**上传端点 `POST /api/upload/:group`
|
||||
// (router.js:101 → controller/upload.js 的 upload())**不在本表**,因此它既不经过本闸门,
|
||||
// 当时也未调用 file_io_guard ⇒ 真正承载考生作品上传的唯一路径**完全无护栏**。
|
||||
// 同表内 `chunk`/`merge` 反倒是**前端已无调用点的死路由**(Upload.vue 于 B2 删除后
|
||||
// 失去唯一调用方,见 api/upload.js 的死代码清理),护栏加在了无人走的路上。
|
||||
//
|
||||
// 性质判定:单次上传是**重 IO**(长连接 + 大文件流式落盘 + 边写边算 md5),
|
||||
// 与 chunk/merge 同类,故**共享同一配额 MAX**(不另开独立上限)——
|
||||
// 配额的意义是「让 worker 有余力 accept 新连接」(见本文件顶部背景),
|
||||
// 三者共享 20 比各自开 20 更能守住这个目的。
|
||||
//
|
||||
// ⚠️ 正则为何必须是**单段** `[^/]+$`(勿"顺手"放宽成 `/^\/api\/upload\//i`):
|
||||
// 同一前缀下还有三条**轻**路由,路径同前缀但**多一段**:
|
||||
// · GET /api/upload/:group/status (查状态,轻)
|
||||
// · GET /api/upload/:group/submissions (列表,轻)
|
||||
// · DELETE /api/upload/:group/:id (删作品,轻)
|
||||
// `[^/]+$` 要求「/api/upload/」之后**恰好一段且到串尾**,故上面三条(两段)**天然不命中**;
|
||||
// 放宽成前缀匹配会把它们一并计入配额,在考生端表现为「查状态也 429」——语义错误。
|
||||
// 本表的 `chunk`/`merge` 同理带 `$` 锚定(既有写法,勿动)。
|
||||
const HEAVY_PATTERNS = [
|
||||
/^\/api\/upload\/[^/]+$/i,
|
||||
/^\/api\/upload\/[^/]+\/chunk$/i,
|
||||
/^\/api\/upload\/[^/]+\/merge$/i,
|
||||
/^\/api\/exam\/[^/]+\/word$/i,
|
||||
|
||||
Reference in New Issue
Block a user