perf(判题): 判题机上同时跑的程序从最多 8 个压到 4 个,挤的时候不再把不超时的判成超时
原来四道闸各管各的(worker 判题 2、api 试运行 2、提交前编译检查 2、worker 里流程图 评分的试运行又 2),api 和 worker 两个进程互相看不见,峰值 8 个程序压在服务器两个核上; 判题机按墙钟判超时,挤到一定程度会冤枉学生。 judge/slots.ts 在 Redis 里按池数名额(每个站点各数各的): - 后台池(正式判题、诊断重跑、流程图评分)上限 JUDGE_CONCURRENCY - 交互池(运行例子、自己输入、生成测试点、编译检查)上限 TRIAL_CONCURRENCY 两池不互相借,正式判题排再长也挤不掉学生的「运行例子」。名额是带到期时间的租约, 进程被杀了到期自动作废;Redis 出错直接放行。正式判题的超时从拿到名额才开始算。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
64994b9499
commit
307b0725d0
4 files changed
+159
-41
No files matched your search
+19
-39
@@ -15,6 +15,7 @@ import type { JudgeJobData } from "./job"
|
||||
import { judgeConfigFor } from "./languages"
|
||||
import { isAccepted, JudgeStatus, type JudgeStatusValue } from "./status"
|
||||
import { parseProblemTemplate, shiftCompileLines } from "./template"
|
||||
import { withJudgeSlot } from "./slots"
|
||||
import { runSqlCase } from "./sql"
|
||||
import {
|
||||
nativeRuntimeError,
|
||||
@@ -121,46 +122,22 @@ export async function postJudge(
|
||||
* 判题机只有 /judge 一个入口,得带一组测试点:给一组空输入,跑的那一下限 1 秒。
|
||||
* 判题机连不上、超时,一律当编得过 —— 那就和原来一样照常格式化。
|
||||
*
|
||||
* 这一下不进判题队列,所以自己排一道:同时最多 COMPILE_CHECK_CONCURRENCY 份在判题机上编,
|
||||
* 其余等着。服务器只有两核,不排的话一个班 40 人一起交 C++ 能把判题机挤到每人 8 秒超时,
|
||||
* 还拖慢正式判题。8 秒是连排队带编译的总预算,排不上也当编得过。
|
||||
* 这一下不进判题队列,但和运行例子共用交互池的名额(judge/slots.ts):服务器只有两核,
|
||||
* 不排的话一个班 40 人一起交 C++ 能把判题机挤到每人 8 秒超时,还拖慢正式判题。
|
||||
* 8 秒是连排队带编译的总预算,排不上也当编得过。
|
||||
*/
|
||||
const COMPILE_CHECK_CONCURRENCY = 2
|
||||
let compileChecks = 0
|
||||
const compileCheckWaiting: (() => void)[] = []
|
||||
|
||||
export async function compileFails(language: string, source: string) {
|
||||
const deadline = AbortSignal.timeout(8000)
|
||||
if (compileChecks < COMPILE_CHECK_CONCURRENCY) {
|
||||
compileChecks++
|
||||
} else {
|
||||
const admitted = await new Promise<boolean>((resolve) => {
|
||||
const giveUp = () => {
|
||||
compileCheckWaiting.splice(compileCheckWaiting.indexOf(wake), 1)
|
||||
resolve(false)
|
||||
}
|
||||
const wake = () => {
|
||||
deadline.removeEventListener("abort", giveUp)
|
||||
resolve(true)
|
||||
}
|
||||
deadline.addEventListener("abort", giveUp, { once: true })
|
||||
compileCheckWaiting.push(wake)
|
||||
})
|
||||
// 被叫醒时名额是直接转交过来的,compileChecks 不用再加
|
||||
if (!admitted) return false
|
||||
}
|
||||
try {
|
||||
const response = await postJudge(language, source, 1000, 256, [{ input: "", output: "" }], {
|
||||
signal: deadline,
|
||||
return await withJudgeSlot("interactive", { holdMs: 30_000, signal: deadline }, async () => {
|
||||
const response = await postJudge(language, source, 1000, 256, [{ input: "", output: "" }], {
|
||||
signal: deadline,
|
||||
})
|
||||
const body = (await response.json()) as JudgeResponse
|
||||
return body.err === "CompileError"
|
||||
})
|
||||
const body = (await response.json()) as JudgeResponse
|
||||
return body.err === "CompileError"
|
||||
} catch {
|
||||
return false
|
||||
} finally {
|
||||
const next = compileCheckWaiting.shift()
|
||||
if (next) next()
|
||||
else compileChecks--
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,13 +187,16 @@ async function requestJudge(
|
||||
) {
|
||||
const timeoutMs = judgeTimeoutMs(timeLimit)
|
||||
try {
|
||||
const response = await postJudge(language, code, timeLimit, memoryLimit, testCase, {
|
||||
output,
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
// 超时从拿到名额才开始算:排队等名额不算判题机没回应
|
||||
return await withJudgeSlot("judge", { holdMs: timeoutMs + 30_000 }, async () => {
|
||||
const response = await postJudge(language, code, timeLimit, memoryLimit, testCase, {
|
||||
output,
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
})
|
||||
const body = await readCapped(response, MAX_RESPONSE_BYTES)
|
||||
if (body === null) throw new Error("判题机返回的内容太大")
|
||||
return JSON.parse(body) as JudgeResponse
|
||||
})
|
||||
const body = await readCapped(response, MAX_RESPONSE_BYTES)
|
||||
if (body === null) throw new Error("判题机返回的内容太大")
|
||||
return JSON.parse(body) as JudgeResponse
|
||||
} catch (error) {
|
||||
// 这句会写进 err_info,学生能看到,别把 "The operation timed out." 原样给他
|
||||
if (error instanceof DOMException && error.name === "TimeoutError") {
|
||||
|
||||
Reference in new issue
Block a user