refactor(判题机): 换成 go-judge,空闲内存从几百 MB 降到 16 MB

原来的 JudgeServer 按核数起 gunicorn worker、每份提交再按核数开进程池,空闲内存跟着核数走
(2 核 52MB、16 核 305MB)。换成 go-judge 后空闲 16MB,同一批提交判题中 80–100MB(原来约 370MB)。

go-judge 只管在沙箱里跑命令,编译、逐点运行、比对输出、定状态码搬到 judge/sandbox.ts,
口径逐条照搬 JudgeServer,出参形状不变。拿生产库真实提交新旧逐点对比:C 2000 份 1989 一致、
C++ 891 份 890 一致、Python 2000 份全一致;不一致的是 system("pause")(现在不再判运行时错误)、
未定义行为、endl 死循环打印(运行时错误变运行超时),见 docker/judge/README.md。

- 判题容器改成 privileged(go-judge 要自己建 cgroup),镜像 oj2-judge-3
- 心跳改由 worker 每 5 秒问判题机 /version 代记,删掉 /api/judge-server/heartbeat
- 前端统一用 stripJudgePath 去掉判题机工作目录(新的 /w/ 和旧的 /judger/run/)
- 提交前编译检查只编译不运行

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-26 15:03:57 +08:00
1 parent 8427475337
commit 37e971b6b4
30 files changed
+881 -592

No files matched your search

+13 -28
View File
@@ -7,9 +7,8 @@
# docker/judge/build.sh --no-cache # 不吃构建缓存
# docker/judge/build.sh --no-mirror # 不走国内镜像源(默认走)
#
# 上游 JudgeServer 停更在 2024-04-05,registry 上的 1.6.1 == latest,没有新版
# 可拉。这个脚本从上游那个固定 commit 拉源码(server/ 和 Judger/ 一行不改),
# 只把 Dockerfile 换成 docker/judge/Dockerfile —— 新工具链的全部改动都在那里。
# 镜像 = go-judge 官方镜像里的那个二进制 + debian 的 gcc / python。构建上下文就是
# docker/judge/ 这个目录(Dockerfile + mount.yaml)。
#
# 上传和切换见 docker/judge/README.md。
@@ -18,15 +17,11 @@ set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
# 上游 master HEAD(= tag v1.6.1+judgeserver.1.6.1)。Judger 子模块的版本由这个
# commit 自己钉住(d19a6dc),不用在这里再写一遍。
UPSTREAM_REPO=https://github.com/QingdaoU/JudgeServer.git
UPSTREAM_COMMIT=b28aa56d60fed7358a29d9bdeb9d86fcc06e41a7
# 我们自己重编的第几版判题镜像(官方那个算第 1 版,这是第 2 版)。
# **改工具链就把末尾的序号 +1**(下一版叫 oj2-judge-3),别在同一个名字上重建 ——
# 我们自己构建的第几版判题镜像(官方 JudgeServer 算第 1 版,自己重编的 JudgeServer
# 是第 2 版,换成 go-judge 是第 3 版)。
# **改工具链就把末尾的序号 +1**(下一版叫 oj2-judge-4),别在同一个名字上重建 ——
# compose 的 `up -d` 不带 --pull,名字没变就会静默用机器上的旧镜像。
IMAGE=oj2-judge-2
IMAGE=oj2-judge-3
SAVE=0
BUILD_ARGS=()
@@ -34,13 +29,12 @@ for arg in "$@"; do
case "$arg" in
--save) SAVE=1 ;;
--no-cache) BUILD_ARGS+=(--no-cache) ;;
# 默认用国内镜像源(Dockerfile 顶部的四个 ARG)。在能直连的网络里用这个关掉,
# 换回 deb.debian.org / pypi.org。
# 默认用国内镜像源(Dockerfile 顶部的 ARG)。在能直连的网络里用这个关掉,
# 换回 deb.debian.org。
--no-mirror)
BUILD_ARGS+=(
--build-arg APT_MIRROR=http://deb.debian.org/debian
--build-arg APT_SECURITY_MIRROR=http://deb.debian.org/debian-security
--build-arg PIP_INDEX_URL=https://pypi.org/simple
) ;;
*) echo "未知参数:$arg(可用:--save、--no-cache、--no-mirror)" >&2; exit 2 ;;
esac
@@ -53,25 +47,16 @@ die() { printf '\n\033[1;31m❌ %s\033[0m\n\n' "$*" >&2; exit 1; }
command -v docker >/dev/null || die "没装 docker"
[ -f docker/judge/Dockerfile ] || die "docker/judge/Dockerfile 不见了,当前目录:$PWD"
src=$(mktemp -d)
trap 'rm -rf "$src"' EXIT
say "拉上游源码 $UPSTREAM_COMMIT"
git -c advice.detachedHead=false clone --quiet "$UPSTREAM_REPO" "$src"
git -C "$src" -c advice.detachedHead=false checkout --quiet "$UPSTREAM_COMMIT"
git -C "$src" submodule update --quiet --init --recursive
ok "server/ 和 Judger/ 就位"
say "构建 $IMAGE"
# 上下文是上游源码,Dockerfile 用我们自己的那份。
docker build "${BUILD_ARGS[@]}" -f docker/judge/Dockerfile -t "$IMAGE" "$src"
docker build "${BUILD_ARGS[@]}" -t "$IMAGE" docker/judge
ok "$IMAGE"
say "镜像里的工具链"
docker run --rm --entrypoint sh "$IMAGE" -c '
printf "gcc %s\n" "$(gcc -dumpfullversion)"
printf "g++ %s\n" "$(g++ -dumpfullversion)"
printf "python3 %s\n" "$(python3 -V | cut -d" " -f2)"
printf "gcc %s\n" "$(gcc -dumpfullversion)"
printf "g++ %s\n" "$(g++ -dumpfullversion)"
printf "python3 %s\n" "$(python3 -V | cut -d" " -f2)"
printf "go-judge %s\n" "$(go-judge -version | head -1)"
'
if [ "$SAVE" = 1 ]; then