Skip to content

判题与沙箱设计

1. 设计目标

  • 将代码运行与正式评测完全隔离于主业务 API。
  • 统一承载“在线 IDE 试运行”和“正式提交评测”两类执行任务。
  • go-judge 结果进行归一化,屏蔽第三方实现细节。
  • 支持重评、超时、编译失败、运行失败、输出超限等典型场景。

2. 组件组成

组件职责
workspaceService读取工作区文件并生成运行 / 提交快照
submissionService接收正式提交并投递评测任务
judge-worker消费运行 / 评测队列、调用 go-judge、归一化结果
go-judge在隔离环境中执行编译和运行
sandbox_runs保存试运行历史与输出
judge_runs保存正式评测历史与结果

3. go-judge 选型原因

  • 官方文档提供统一的 REST、WebSocket 和 gRPC 接口,便于后续扩展。
  • 支持 Linux namespace 与 cgroup 限制,满足课程作业隔离执行要求。
  • 支持 copyIn / copyOut / copyOutCached,可将编译产物缓存为 fileId,减少隐藏用例重复编译开销。
  • 支持 pipeMapping,为后续交互题或多进程题型保留扩展空间。
  • 支持容器池和并行度配置,适合“课程高峰期短时并发”的场景。

4. 执行模式设计

4.1 在线 IDE 试运行

  • 输入来源:当前工作区快照 + 公开样例或学员自定义输入。
  • 输出结果:stdoutstderr、退出状态、耗时、内存、编译输出摘要。
  • 写入位置:sandbox_runs
  • 业务边界:不生成正式成绩,不触发教师通知,不进入正式评测统计。

4.2 正式提交评测

  • 输入来源:正式提交绑定的不可变快照 + 隐藏用例 + 语言配置。
  • 输出结果:结构化测试点结果、得分、判题摘要、原始响应。
  • 写入位置:judge_runssubmissions.current_judge_run_id
  • 业务边界:作为自动评分依据,可被重评和人工批改引用。

5. 队列任务模型

5.1 试运行任务

json
{
  "runId": "run_xxx",
  "workspaceId": "ws_xxx",
  "snapshotId": "snap_xxx",
  "taskId": "task_xxx",
  "languageProfile": {
    "language": "python3",
    "runCommand": ["/usr/bin/python3", "main.py"]
  },
  "stdin": "1 2",
  "limits": {
    "cpuTimeMs": 2000,
    "clockTimeMs": 3000,
    "memoryMb": 256,
    "outputKb": 1024
  }
}

5.2 正式评测任务

json
{
  "judgeRunId": "jr_xxx",
  "submissionId": "sub_xxx",
  "snapshotId": "snap_xxx",
  "taskId": "task_xxx",
  "languageProfileId": "lang_xxx",
  "testcases": [
    { "id": "tc1", "inputKey": "cases/1.in", "outputKey": "cases/1.out", "weight": 20 }
  ],
  "limits": {
    "cpuTimeMs": 2000,
    "clockTimeMs": 4000,
    "memoryMb": 256,
    "outputKb": 1024
  }
}

6. go-judge 请求封装策略

6.1 编译阶段

  • 对编译型语言先下发一次编译请求。
  • copyIn 注入源文件与必要依赖文件。
  • copyOutCached 导出可执行产物,记录返回的 fileId
  • 编译失败时直接结束本次评测,并将 compile_error 写入归一化结果。

6.2 执行阶段

  • 执行请求通过 copyIn.fileId 引用编译产物,避免重复上传。
  • 标准输入以匿名文件注入;标准输出和标准错误通过命名文件收集。
  • 每个隐藏用例独立执行,便于输出逐测试点结果。
  • 评测完成后主动清理编译缓存 fileId

7. 结果归一化模型

字段说明
statussuccess / failed / timeout / compile_error / runtime_error / output_limit_exceeded
score归一化后的总分
passedCount通过测试点数量
totalCount测试点总数
compileMessage编译错误摘要
runtimeMessage运行错误摘要
caseResults每个测试点的输入、输出、判定结果、耗时和内存

7.1 判定映射示例

go-judge 状态平台内部判定
Acceptedaccepted
Time Limit Exceededtime_limit_exceeded
Memory Limit Exceededmemory_limit_exceeded
Output Limit Exceededoutput_limit_exceeded
Dangerous Syscallsystem_error
Non Zero Exit Status / Signalledruntime_error
Internal Error / File Errorsystem_error

8. 资源限制建议

维度试运行默认值正式评测默认值
单次 CPU 时间2 秒2 秒 / 用例
单次时钟时间3 秒4 秒 / 用例
单题总评测时间10 秒60 秒
内存限制256 MB256 MB
输出限制1 MB1 MB
并发限制同一学员同一时刻 1 个试运行同一学员同一任务 1 个运行中评测

9. 部署与容量约束

  • go-judge 生产部署要求 Linux Kernel >= 3.10,并可用 cgroup。
  • 容器化部署时需保证嵌套容器 / cgroup 能力,避免资源限制失效。
  • go-judge 默认并发度接近 CPU 数量,可通过 -parallelism 调整。
  • 缓存文件存储在共享内存时,应根据并发和编译产物大小预留 /dev/shm
  • Windows / macOS 支持为实验性质,不作为正式交付环境。

10. 故障恢复

  • go-judge 不可用:任务标记失败,进入重试或人工重评。
  • Worker 崩溃:依赖队列确认机制重新消费未确认任务。
  • 归一化失败:保留原始响应并写入错误日志,供人工排查。
  • 编译缓存泄漏:通过后台清理任务和 go-judge 文件 TTL 回收。

11. 安全要求

  • 运行环境默认禁用外网访问。
  • 禁止挂载主机敏感目录。
  • 限制 CPU、内存、进程数、打开文件数和临时文件空间。
  • 对任务输入、源码、输出统一做大小限制。
  • go-judge 原始接口只允许内网访问,浏览器永远不直接调用。