From a9af26c2d6fdd3226dd8d5c16984c01a8abe617e Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Fri, 23 Oct 2026 10:20:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20CI=EF=BC=9A=E8=AF=84?= =?UTF-8?q?=E5=88=86=E7=82=B9=E8=A1=A8=E6=A0=BC=E5=89=8D=E7=AD=89=E9=82=A3?= =?UTF-8?q?=E4=B8=80=E8=A1=8C=E5=87=BA=E7=8E=B0=EF=BC=9B=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E5=87=BA=E9=94=99=E6=97=B6=E4=BC=98=E5=85=88=E6=8A=A5=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=EF=BC=9BCPU=20=E8=B6=85=E6=97=B6=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E7=94=9F=E6=88=90=20core=20dump?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 评分的"点 / 修改表格单元格"步骤最多等 5 秒,让上一步(保存后重新查询)刷新出的行出现; CI(2 核、整套测试同时跑)上 p6-t2"删除"一项因此偶发失败 - 检查项不通过时,如果程序抛过异常,报"程序出错:…"(异常消息可能比失败的检查晚到) - 学生程序的 CPU 时间上限软硬相同,到时直接 SIGKILL,并且不生成 core dump: 原来先发 SIGXCPU,每个死循环都留下几 MB 的转储,本机桌面还会弹出"程序崩溃" Co-Authored-By: Claude Opus 5.5 --- server/src/grading/grader.ts | 10 +++++++++- server/src/sandbox.ts | 16 ++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/server/src/grading/grader.ts b/server/src/grading/grader.ts index bd73691..6c0857d 100644 --- a/server/src/grading/grader.ts +++ b/server/src/grading/grader.ts @@ -8,6 +8,8 @@ import type { Check, Expectation, Step } from './checks' import { ProgramDriver } from './driver' const GRADE_CONCURRENCY = 6 +/** 点数据表格的单元格前,最多等多久让那一行出现(程序可能还在查询数据库、刷新表格) */ +const GRID_WAIT = 5000 /** 程序启动后多久内应该显示出窗体 */ const START_TIMEOUT = 10_000 @@ -63,7 +65,11 @@ async function runCheck(dir: string, check: Check, sampleId?: string): Promise gridRows(need(driver, step.control)).length > step.row, GRID_WAIT) const rows = gridRows(node) if (step.row >= rows.length) throw new Failure(`数据表格 ${step.control} 里没有第 ${step.row + 1} 行(现在有 ${rows.length} 行数据)`) const column = typeof step.column === 'number' ? step.column : gridColumns(node).findIndex((c) => c.header === step.column || c.name === step.column) diff --git a/server/src/sandbox.ts b/server/src/sandbox.ts index c3821a8..66e5947 100644 --- a/server/src/sandbox.ts +++ b/server/src/sandbox.ts @@ -76,8 +76,9 @@ export function runCommand(projectDir: string, extraEnv: Record, const cmd = [ // 学生程序写出死循环时,降低它的优先级,网页服务和编译服务仍然流畅 'nice', '-n', '10', - // CPU 时间用完先收到 SIGXCPU 结束,过几秒还在就强制结束 - 'prlimit', `--cpu=${config.cpuLimitSeconds}:${config.cpuLimitSeconds + 5}`, + // CPU 时间用完直接 SIGKILL 结束(软硬上限相同)。软上限更小时内核先发 SIGXCPU,它默认生成 core dump: + // 学生每写一次死循环就留下一个转储,本机桌面还会弹出"程序崩溃" + 'prlimit', `--cpu=${config.cpuLimitSeconds}:${config.cpuLimitSeconds}`, '--core=0', ...bwrapArgs(projectDir), ...(dbDir ? ['--bind', dbDir, DB_DIR] : []), 'dotnet', `${RUNNER_DIR}/${config.runnerDll.split('/').pop()}`, 'run', APP_DIR, @@ -85,12 +86,15 @@ export function runCommand(projectDir: string, extraEnv: Record, return { cmd, env } } -const SIGXCPU = 24 +const SIGKILL = 9 -/** 运行进程退出时,如果是因为 CPU 时间用完,给学生的说明 */ +/** + * 运行进程退出时,如果是因为 CPU 时间用完,给学生的说明。 + * CPU 上限到时内核发 SIGKILL,bwrap 把子进程"被信号结束"转成退出码 128 + 信号编号; + * 平台自己结束程序(停止、超时、关闭页面)用的是 SIGTERM,不会误报。 + */ export function cpuLimitMessage(code: number | null, signal: string | number | null) { - // bwrap 把子进程"被信号结束"转成退出码 128 + 信号编号 - return signal === 'SIGXCPU' || code === 128 + SIGXCPU + return signal === 'SIGKILL' || code === 128 + SIGKILL ? `\n程序占用 CPU 太多,已被结束(累计超过 ${config.cpuLimitSeconds} 秒)。检查是否有死循环:循环里要有退出条件,等待请用 Timer 或 Thread.Sleep。\n` : undefined }