Skip to content

ThinkSpiritLab/Heng-Client

Repository files navigation

众衡

本系统是为 ThinkSpirit 实验室的下一代在线评测系统设计的评测机系统的评测端。专职负责运行用户程序并得出判定结论。

系统的详细结构参见协议仓库, 参考的控制端实现在 Heng-Controler

本仓库的 Docker 镜像(语言环境已封装)见 https://hub.docker.com/r/thinkspiritlab/heng-client

部署

推荐关闭宿主机的 Swap。

Docker

Host的内核版本需大于 4.6 (支持cgroup_namespaces)

dnf install --assumeyes yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
dnf install --assumeyes docker-ce
systemctl start docker
systemctl enable docker
docker pull thinkspiritlab/heng-client:latest
docker run --cgroupns private --privileged -it -v $(pwd)/config.toml:/hc/config/config.toml thinkspiritlab/heng-client
# docker run --cgroupns private --privileged -d --restart=always -v $(pwd)/config.toml:/hc/config/config.toml thinkspiritlab/heng-client

CentOS8

cd ~
dnf install git -y
git clone https://github.com/ThinkSpiritLab/Heng-Client.git
cd ./Heng-Client
bash prepare-centos8.sh
cp config/config.example.toml config/config.toml
npm run start # pm2 start ./dist/index.js --name judger

Ubuntu20.04

sudo -i
cd ~
apt update
apt install git -y
git clone https://github.com/ThinkSpiritLab/Heng-Client.git
cd ./Heng-Client
bash prepare-ubuntu20.sh
cp config/config.example.toml config/config.toml
npm run start # pm2 start ./dist/index.js --name judger

依赖

沙盒部分

本系统依赖 nsjailHeng-Core 作为沙盒内核。需要在配置文件中指定对应路径。

它的仓库是 https://github.com/google/nsjailhttps://github.com/ThinkSpiritLab/Heng-Core

语言

为了评测机正常工作,需要其支持的各种语言环境。

运行时

开发使用 nodejs:14 不保证在更低版本下能够运行。

架构

基础设施

沙盒运行支持

使用 nsjailHeng-Core

语言支持

对于每种语言,实现一个 Language 类型的函数并在 src/Spawn/Language/index.tsimport 并注册。

限流

Throttle 模块实现了异步限流,调用方法是将逻辑封装在异步函数中发送给 withThrottle 方法。

业务逻辑

控制端模块

src/controller.ts 中实现了控制端相关逻辑。

调用时,实例化一个 Controller 对象,注册各类评测机方法后获取 token 并开启连接。

评测

仅通过 ExecutableAgent 调用外部程序,ExecutableAgent 有编译和运行功能;ExecutableAgent 传入可执行对象 Executable(包括代码、语言、限制),根据不同语言调用相应 Language。编译或运行时可提供输入输出流,ExecutableAgent 询问 Language 编译参数或运行参数,并作一些合并,然后执行。

对于 NormalSpj,用户程序的输出被重定向到文件,随后被提供给结果判断程序;对于 Interactiveuserinteractor 同时运行,双方的输入输出被 pipe 到另一方,由于管道缓冲区有容量限制,写满后写程序阻塞,要避免大量输入输出、避免 interactor 过慢、避免 interactor 时限小于 user

对于 NormalSpj,用户程序没有正常结束运行时,跳过执行结果判断程序。

其他

spj 返回值及输出结果汇总

#ifndef OK_EXIT_CODE
#   ifdef CONTESTER
#       define OK_EXIT_CODE 0xAC
#   else
#       define OK_EXIT_CODE 0
#   endif
#endif

#ifndef WA_EXIT_CODE
#   ifdef EJUDGE
#       define WA_EXIT_CODE 5
#   elif defined(CONTESTER)
#       define WA_EXIT_CODE 0xAB
#   else
#       define WA_EXIT_CODE 1
#   endif
#endif

#ifndef PE_EXIT_CODE
#   ifdef EJUDGE
#       define PE_EXIT_CODE 4
#   elif defined(CONTESTER)
#       define PE_EXIT_CODE 0xAA
#   else
#       define PE_EXIT_CODE 2
#   endif
#endif

#ifndef FAIL_EXIT_CODE
#   ifdef EJUDGE
#       define FAIL_EXIT_CODE 6
#   elif defined(CONTESTER)
#       define FAIL_EXIT_CODE 0xA3
#   else
#       define FAIL_EXIT_CODE 3
#   endif
#endif

#ifndef DIRT_EXIT_CODE
#   ifdef EJUDGE
#       define DIRT_EXIT_CODE 6
#   else
#       define DIRT_EXIT_CODE 4
#   endif
#endif

#ifndef POINTS_EXIT_CODE
#   define POINTS_EXIT_CODE 7
#endif

#ifndef UNEXPECTED_EOF_EXIT_CODE
#   define UNEXPECTED_EOF_EXIT_CODE 8
#endif

#ifndef PC_BASE_EXIT_CODE
#   ifdef TESTSYS
#       define PC_BASE_EXIT_CODE 50
#   else
#       define PC_BASE_EXIT_CODE 0
#   endif
#endif

int resultExitCode(TResult r) {
    if (r == _ok)
        return OK_EXIT_CODE;
    if (r == _wa)
        return WA_EXIT_CODE;
    if (r == _pe)
        return PE_EXIT_CODE;
    if (r == _fail)
        return FAIL_EXIT_CODE;
    if (r == _dirt)
        return DIRT_EXIT_CODE;
    if (r == _points)
        return POINTS_EXIT_CODE;
    if (r == _unexpected_eof)
#ifdef ENABLE_UNEXPECTED_EOF
        return UNEXPECTED_EOF_EXIT_CODE;
#else
        return PE_EXIT_CODE;
#endif
    if (r >= _partially)
        return PC_BASE_EXIT_CODE + (r - _partially);
    return FAIL_EXIT_CODE;
}


switch (result) {
    case _ok:
        errorName = "ok ";
        quitscrS(LightGreen, errorName);
        break;
    case _wa:
        errorName = "wrong answer ";
        quitscrS(LightRed, errorName);
        break;
    case _pe:
        errorName = "wrong output format ";
        quitscrS(LightRed, errorName);
        break;
    case _fail:
        errorName = "FAIL ";
        quitscrS(LightRed, errorName);
        break;
    case _dirt:
        errorName = "wrong output format ";
        quitscrS(LightCyan, errorName);
        result = _pe;
        break;
    case _points:
        errorName = "points ";
        quitscrS(LightYellow, errorName);
        break;
    case _unexpected_eof:
        errorName = "unexpected eof ";
        quitscrS(LightCyan, errorName);
        break;
    default:
        if (result >= _partially) {
            errorName = format("partially correct (%d) ", pctype);
            isPartial = true;
            quitscrS(LightYellow, errorName);
        } else
            quit(_fail, "What is the code ??? ");

其他平台编译参数汇总

luogu:https://www.luogu.com.cn/discuss/86673

codeforces(may be old):https://codeforces.com/blog/entry/79

loj:https://github.com/syzoj/syzoj-ng-judge/tree/master/src/languages

lojv3:https://github.com/syzoj/judge-v3/tree/master/src/languages

uoj:https://github.com/UniversalOJ/UOJ-System/blob/230738b770022cc6b882c42b67b82d7b29b82003/judger/uoj_judger/include/uoj_judger.h#L1137

pta: https://github.com/pintia/ljudge/tree/master/etc/ljudge

todo

  • spj cache LRU