Files
xuyueandClaude Opus 5.5 37e971b6b4 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>
2026-10-26 15:03:57 +08:00

84 lines
4.6 KiB
Docker
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 判题沙箱镜像:go-judge(https://github.com/criyle/go-judge)+ 我们要的工具链。
#
# go-judge 只管「在沙箱里跑一条命令」:编译、逐个测试点运行、比对输出、算状态码
# 都在 api 那边(apps/api/src/judge/sandbox.ts)。所以这个镜像里只有两样东西:
# go-judge 的单个二进制,和学生程序要用的编译器 / 解释器。
#
# 2026-10 从 QingdaoU/JudgeServer 换过来的(那边停更在 2024-04,而且 gunicorn 按
# 核数起 worker,16 核的机器空闲就占 300MB)。换的原因和逐份对比的结论见 README.md。
#
# gcc / g++ 14 (trixie 默认)
# Python 3.13 (trixie 默认)
#
# ⚠️ 语言的编译/运行命令**不在这个文件里**,在 apps/api/src/judge/languages.ts。
# 升 gcc 大版本要同步看那边的开关(gcc-14 把 implicit-function-declaration
# 等提成了 error,languages.ts 里有几个 -Wno-error 把它压回去)。
# ⚠️ 换 Python 大版本时,api 镜像(docker/Dockerfile 的 python3-minimal)要一起换,
# 理由见 README.md。
# go-judge 的版本。升级前看一眼它的 release notes 里有没有改 /run 的出入参。
ARG GO_JUDGE_VERSION=v1.13.3
# 镜像源。默认走国内镜像 —— 官方源在这边实测 197 KB/s,清华 3.9 MB/s。
# 出国内网络环境用 build.sh --no-mirror 关掉。
#
# ⚠️ 只能用 **http**:改 sources 这一步发生在装 ca-certificates 之前,
# base 镜像里没有 CA 根证书,https 一律 `certificate verify failed`。
ARG APT_MIRROR=http://mirrors.tuna.tsinghua.edu.cn/debian
ARG APT_SECURITY_MIRROR=http://mirrors.tuna.tsinghua.edu.cn/debian-security
FROM criyle/go-judge:${GO_JUDGE_VERSION} AS go-judge
FROM debian:trixie-slim
ARG TARGETARCH
ARG TARGETVARIANT
ARG APT_MIRROR
ARG APT_SECURITY_MIRROR
ENV DEBIAN_FRONTEND=noninteractive
RUN --mount=type=cache,target=/var/cache/apt,id=apt-cahce-1-$TARGETARCH$TARGETVARIANT-final,sharing=locked \
--mount=type=cache,target=/var/lib/apt,id=apt-cahce-2-$TARGETARCH$TARGETVARIANT-final,sharing=locked \
<<EOS
set -ex
rm -f /etc/apt/apt.conf.d/docker-clean
echo 'Binary::apt::APT::Keep-Downloaded-Packages "1";' > /etc/apt/apt.conf.d/keep-cache
echo 'APT::Install-Recommends "0";' > /etc/apt/apt.conf.d/no-recommends
echo 'APT::AutoRemove::RecommendsImportant "0";' >> /etc/apt/apt.conf.d/no-recommends
# debian-security 必须先换:它的 URI 以 debian 的 URI 为前缀,
# 反过来的话第二条 sed 会把它改成 <镜像>/debian-security,路径不存在。
sed -i "s|http://deb.debian.org/debian-security|$APT_SECURITY_MIRROR|; s|http://deb.debian.org/debian|$APT_MIRROR|" /etc/apt/sources.list.d/debian.sources
apt-get update
# 和换掉的 JudgeServer 镜像同一组包(去掉了它自己要的 venv / dev / strace),
# 学生程序能 import 的标准库模块因此一个不少
apt-get install -y python3.13-minimal libpython3.13-stdlib gcc-14 g++-14
# languages.ts 里的编译/运行命令写的是绝对路径(/usr/bin/gcc、/usr/bin/python3 …),
# 全靠下面这几条 alternatives 挂出来。换包名必须同步改,漏一条的表现是 CE 而不是报错。
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 14
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 14
update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.13 13
EOS
COPY --from=go-judge /opt/go-judge /usr/local/bin/go-judge
COPY mount.yaml /etc/go-judge/mount.yaml
# 构建期自检:任何一个不是预期版本,说明上面的包名或 alternatives 配错了。
RUN <<EOS
set -ex
gcc --version
g++ --version
python3 --version
go-judge -version
EOS
# 参数也能用环境变量覆盖(ES_ 前缀,比如 ES_PARALLELISM=4),compose 里就是这么传 token 的。
# -http-addr 端口沿用 JudgeServer 的 8080,compose 和 JUDGE_SERVER_URL 不用改
# -mount-conf 沙箱里能看到哪些目录,见 mount.yaml
# -src-prefix 学生程序的 stdin 直接从 /test_case 里开,不经 api 中转;别的路径一律拒
# -output-limit 每条命令写文件的上限(RLIMIT_FSIZE)。16MB 是 JudgeServer 的口径:
# 死循环打印写到 16MB 吃 SIGXFSZ,判运行时错误,前端有对应的中文说明
# -file-timeout 编译产物和输出由 api 用完就删,这是 api 半路崩掉时的兜底
# -dir 文件存储默认在 /dev/shm,docker 默认只给 64MB,放到 /tmp(compose 里的 tmpfs)
ENTRYPOINT [ "go-judge", "-http-addr=:8080", "-mount-conf=/etc/go-judge/mount.yaml", "-src-prefix=/test_case", "-output-limit=16m", "-file-timeout=10m", "-dir=/tmp/go-judge" ]
EXPOSE 8080