Judge0 1.13.1 配置参数详解

逐项拆解 Judge0 1.13.1 的 judge0.conf 全部配置项:数据库与 Redis 连接、RAILS_MAX_THREADS/RAILS_SERVER_PROCESSES 空值强转崩溃、COUNTRIES 与 ALLOW_ORIGINS、AUTHN_HEADER 与 AUTHN_TOKEN、INTERVAL 队列轮询、CPU/WALL/MEMORY/STACK 限制与 MAX_PROCESSES、MAX_FILE_SIZE、ENABLE_WAIT_RESULT 同步开关、CALLBACKS_ENABLED 与回调参数、MAINTENANCE_MODE 及其批量提交绕过与中文消息 500 缺陷;附生产配置模板与 7 类实测问题。

Judge0Judge0代码评测RubyRailsPostgreSQL2026/10/27

Judge0 1.13.1 配置参数详解

目标:逐项拆解 Judge0 1.13.1 judge0.conf 的全部配置项,给出默认值、生效范围(服务端 / 消费者 / 数据库客户端)与本地实测行为。文中的开关实测在 8142 实例(与 8141 基线实例共用同一份代码与数据库,仅环境变量不同)上完成,涉及语言运行时的用例在装有完整运行时的 8138 实例上执行。

一、环境信息

项值
Judge0 版本1.13.1
配置文件/opt/judge0/judge0.conf(被 systemd 以 EnvironmentFile 加载)
基线实例8141,除连接信息外全部保持仓库默认值
开关实验实例8142,同代码同库,仅覆盖待验证的环境变量
语义用例实例8138,装有完整语言运行时(Python 3.8.1 / C++ GCC 9.2.0)
数据库PostgreSQL 14.24,judge0 库
队列Redis 6.0.16,队列键 resque:queue:1.13.1

二、前置条件

  • 已按 Ubuntu 安装篇完成服务端与消费者的部署,/about 返回 1.13.1;
  • 有一份可用的 judge0.conf,且 SECRET_KEY_BASE 已持久化;
  • 能重启服务进程(多数开关在启动时读取,运行时修改无效)。

三、参数查看与修改方式

3.1 配置文件是「环境变量清单」

judge0.conf 并不是 YAML/INI,而是一份可以被 shell source 的键值清单。应用通过 app/helpers/config.rb 读取 ENV,因此:

  1. 配置项的唯一生效入口是环境变量(systemd EnvironmentFile、shell export、进程管理器的环境注入等价);
  2. 修改 .conf 后必须重启对应进程,Puma 与 Resque 都是启动时求值;
  3. 注释里标注的 Default: 是「空值时会回落到什么」,而回落逻辑写在代码里,见 3.2。

3.2 空值回落:两套不同的实现

app/helpers/config.rb 对几乎所有数值/布尔项都用了 .presence || 兜底:

CPU_TIME_LIMIT = (ENV["CPU_TIME_LIMIT"].presence || 5).to_f
MAX_QUEUE_SIZE = (ENV["MAX_QUEUE_SIZE"].presence || 100).to_i
ENABLE_CALLBACKS = ENV["ENABLE_CALLBACKS"] != "false"

但有几处直接强转,空字符串会立刻抛异常:

# config/puma.rb
threads_count  = Integer(ENV['RAILS_MAX_THREADS'])
process_count  = Integer(ENV['RAILS_SERVER_PROCESSES'])
# config/initializers/resque.rb
Resque.redis = Redis.new(port: ENV["REDIS_PORT"], password: ENV["REDIS_PASSWORD"])
# config/secrets.yml
authn_header: <%= ENV["AUTHN_HEADER"] %>
authn_token:  <%= ENV["AUTHN_TOKEN"].to_s.strip %>

实测后果见第七章的第 7.1–7.4 条。这也是「打包发行版里留空没事、裸机部署留空出事」的根本原因:打包路径的启动脚本会先补默认值再 source。

3.3 运行期可查的配置快照

/config_info 返回服务端实际生效的开关与限制(不含密码类):

$ curl -s http://127.0.0.1:8138/config_info | python3 -m json.tool | head -20
{
    "maintenance_mode": false,
    "enable_wait_result": true,
    "enable_compiler_options": true,
    "allowed_languages_for_compile_options": [],
    "enable_command_line_arguments": true,
    "enable_submission_delete": false,
    "enable_callbacks": true,
    "callbacks_max_tries": 3,
    "callbacks_timeout": 5.0,
    "enable_additional_files": true,
    "max_queue_size": 100,
    "cpu_time_limit": 5.0,
    "max_cpu_time_limit": 15.0,
    "cpu_extra_time": 1.0,
    "max_cpu_extra_time": 5.0,
    "wall_time_limit": 10.0
}

这是核对「配置到底有没有生效」最快的手段,比翻日志可靠。

四、参数总览

4.1 服务端行为开关

参数默认值作用域说明
MAINTENANCE_MODEfalse服务端只拦截 POST /submissions 与 DELETE /submissions/:token
MAINTENANCE_MESSAGEJudge0 is currently in maintenance.服务端维护期返回的 error 文本
ENABLE_WAIT_RESULTtrue服务端关闭后 wait=true 返回 400
ENABLE_COMPILER_OPTIONStrue服务端是否允许提交自定义编译选项
ALLOWED_LANGUAGES_FOR_COMPILER_OPTIONS空(全部)服务端白名单语言名,空格分隔
ENABLE_COMMAND_LINE_ARGUMENTStrue服务端是否允许提交命令行参数
ENABLE_SUBMISSION_DELETEfalse服务端是否允许 DELETE /submissions/:token
ENABLE_BATCHED_SUBMISSIONStrue服务端是否开放 /submissions/batch
MAX_SUBMISSION_BATCH_SIZE20服务端单次批量提交/查询的上限
ENABLE_CALLBACKStrue服务端 + 消费者是否发送回调
CALLBACKS_MAX_TRIES3消费者回调失败重试次数
CALLBACKS_TIMEOUT5消费者单次回调超时(秒)
ENABLE_ADDITIONAL_FILEStrue服务端是否允许 additional_files
SUBMISSION_CACHE_DURATION1服务端只缓存按 token 的查询结果,不做提交去重
USE_DOCS_AS_HOMEPAGEfalse服务端是否把文档页设为首页
JUDGE0_TELEMETRY_ENABLEtrue服务端遥测上报开关
RESTART_MAX_TRIES10启动脚本启动失败重试次数(仅打包启动脚本使用)

4.2 访问控制

参数默认值作用域说明
ALLOW_ORIGIN / DISALLOW_ORIGIN空服务端CORS 白/黑名单,空格分隔
ALLOW_IP / DISALLOW_IP空服务端来源 IP 白/黑名单,按 request.remote_ip 判断
AUTHN_HEADERX-Auth-Token(仅打包脚本补)服务端鉴权头名,见 7.2
AUTHN_TOKEN空(关闭鉴权)服务端支持空格分隔多令牌
AUTHZ_HEADERX-Auth-User(仅打包脚本补)服务端授权头名
AUTHZ_TOKEN空(受保护接口全部 403)服务端授权令牌,未配置时受保护接口不可用

4.3 队列与消费者

参数默认值作用域说明
INTERVAL0.1消费者轮询间隔,空值会导致消费者静默退出
COUNT2*nproc消费者并行消费者数量,仅打包启动脚本使用
MAX_QUEUE_SIZE100服务端队列上限,超出返回 503 queue is full
QUEUE / JUDGE0_VERSION空 / 版本号消费者 + 服务端Resque 队列名,同时是 /about 的版本字段

4.4 执行限制

参数默认值上限项默认值实测说明
CPU_TIME_LIMIT5MAX_CPU_TIME_LIMIT=15单次提交 CPU 时间(秒,可小数)
CPU_EXTRA_TIME1MAX_CPU_EXTRA_TIME=5超时后额外宽限,用于报告真实耗时
WALL_TIME_LIMIT10MAX_WALL_TIME_LIMIT=20墙钟时间,防睡眠程序
MEMORY_LIMIT128000(KB)MAX_MEMORY_LIMIT=512000地址空间/内存 cgroup 上限
STACK_LIMIT64000(KB)MAX_STACK_LIMIT=128000进程栈上限
MAX_PROCESSES_AND_OR_THREADS60MAX_MAX_PROCESSES_AND_OR_THREADS=120依赖 cgroup 的进程数上限
ENABLE_PER_PROCESS_AND_THREAD_TIME_LIMITfalseALLOW_...=true计时按进程/线程而非总量
ENABLE_PER_PROCESS_AND_THREAD_MEMORY_LIMITfalseALLOW_...=true内存按进程/线程而非总量
MAX_FILE_SIZE1024(KB)MAX_MAX_FILE_SIZE=4096可写文件大小上限
NUMBER_OF_RUNS1MAX_NUMBER_OF_RUNS=20重复运行取平均
REDIRECT_STDERR_TO_STDOUTfalse—把 stderr 并入 stdout
MAX_EXTRACT_SIZE10240(KB)—附加文件解压总量上限
ALLOW_ENABLE_NETWORKtrue—是否允许提交方申请联网
ENABLE_NETWORKfalse—默认是否允许程序联网

4.5 连接与凭证

参数默认值作用域说明
REDIS_HOST / REDIS_PORTlocalhost / 6379服务端 + 消费者端口空值会触发 Float("") 异常
REDIS_PASSWORD无默认值(必填)服务端 + 消费者直接传入 Redis.new
POSTGRES_HOST / POSTGRES_PORTlocalhost / 5432服务端 + 消费者database.yml 直接切分该值
POSTGRES_DB / POSTGRES_USER / POSTGRES_PASSWORDpostgres / postgres / 必填服务端 + 消费者生产模式生效

4.6 Rails 与服务进程

参数默认值作用域说明
RAILS_ENVproduction全部决定 database.yml 读取哪一段
RAILS_MAX_THREADSnproc服务端Puma 线程数 = 数据库连接池大小
RAILS_SERVER_PROCESSES2服务端Puma cluster 进程数
SECRET_KEY_BASE随机生成(打包脚本)服务端裸机部署必须持久化,否则重启后会话/缓存签名变化
PORT2358服务端不在 judge0.conf 中,只能通过环境变量或 -p 传入

五、核心参数分类详解

5.1 维护模式与 wait 语义

维护模式只覆盖两个动作。源码里写死了白名单:

# app/controllers/submissions_controller.rb:3
before_action :check_maintenance, only: [:create, :destroy]

实测(MAINTENANCE_MODE=true,消息为 ASCII):

$ curl -s -w " [%{http_code}]" -X POST http://127.0.0.1:8142/submissions \
    -H "Content-Type: application/json" \
    -d '{"source_code":"print(1)","language_id":71}'
{"error":"Judge0 maintenance window, try later"} [503]

但批量提交不受拦截:

$ curl -s -w " [%{http_code}]" -X POST http://127.0.0.1:8142/submissions/batch \
    -H "Content-Type: application/json" \
    -d '{"submissions":[{"source_code":"print(2)","language_id":71}]}'
[{"token":"3659b9eb-54e1-4980-b872-de0dcd37fa15"}] [201]

GET /about 在维护期仍返回 200,可作为健康检查用。另一处坑:MAINTENANCE_MESSAGE 写中文时,check_maintenance 渲染会抛编码异常,POST /submissions 返回 500 而不是 503:

FATAL -- : Encoding::UndefinedConversionError ("\xE7" from ASCII-8BIT to UTF-8):
FATAL -- : app/controllers/sessions_controller.rb:35:in `check_maintenance'

结论:维护消息保持 ASCII,否则维护期会变成「500 而不是可读的 503」。

ENABLE_WAIT_RESULT=false 关闭同步等待:

$ curl -s -w " [%{http_code}]" -X POST "http://127.0.0.1:8142/submissions?wait=true" ...
{"error":"wait not allowed"} [400]

不带 wait 的提交仍返回 201 与 token。

💡 技术拓展:wait=true 时 IsolateJob 在 API 进程内联执行(perform_now),不走队列,因此不受 MAX_QUEUE_SIZE 约束,也不消耗消费者。这让「同步提交」在流量高峰时会直接占满 Puma 线程——RAILS_MAX_THREADS 实际上成了同步提交的并发上限。需要抗压时应关闭 ENABLE_WAIT_RESULT,全部走异步。

5.2 队列上限与消费者节奏

MAX_QUEUE_SIZE 的判定发生在创建提交时,统计口径是「当前队列长度 + 本次提交数」:

if Resque.size(ENV["JUDGE0_VERSION"]) + number_of_submissions > Config::MAX_QUEUE_SIZE

把 MAX_QUEUE_SIZE 调成 2 并停掉消费者后连续提交四次:

submit1: 201 {"token":"20c6260c-076b-49cd-9f5e-a15a66b768c9"}
submit2: 201 {"token":"faaa073d-d23b-4875-9a8e-09de16f096f0"}
submit3: 503 {"error":"queue is full"}
submit4: 503 {"error":"queue is full"}

此时队列键的实际长度:

$ redis-cli -a judge0_redis_pwd_2026 keys "resque:queue:*"
resque:queue:1.13.1
$ redis-cli -a judge0_redis_pwd_2026 llen "resque:queue:1.13.1"
2

注意队列键带 resque:queue: 前缀,直接 llen 1.13.1 会得到 0,误判为「队列空但被拒绝」。

INTERVAL 决定消费者轮询间隔,默认 0.1 秒。它必须显式设值:空值时 Resque::Worker#work 的 Float(interval) 抛异常,被 rescue Exception 吞掉,进程以 0 退出(见 7.4)。

COUNT 只在打包启动脚本里生效(rake resque:workers),systemd 直接跑 resque:work 时不起作用,并行度由单元实例数决定。

5.3 鉴权与授权

AUTHN_TOKEN 配置后,所有接口(含 GET /about)都需要携带令牌;令牌可放在请求头或查询参数:

$ curl -s -w " [%{http_code}]" http://127.0.0.1:8142/about
 [401]

$ curl -s -w " [%{http_code}]" -H "X-Auth-Token: test_token_2026" http://127.0.0.1:8142/about
{"version":"1.13.1","homepage":null,"source_code":null,"maintainer":null} [200]

$ curl -s -w " [%{http_code}]" "http://127.0.0.1:8142/about?X-Auth-Token=test_token_2026"
{"version":"1.13.1","homepage":null,"source_code":null,"maintainer":null} [200]

401 的响应体为空,排查时不要去找 JSON 错误信息。

AUTHN_HEADER 必须在裸机部署时显式写 X-Auth-Token:config/secrets.yml 直接取 ENV["AUTHN_HEADER"],没有默认值。实测只配 AUTHN_TOKEN 而未配 AUTHN_HEADER 时,带正确令牌也返回 401——因为头名是空串,永远匹配不到。

AUTHZ_* 用于受保护的管理类接口(如删除提交),AUTHZ_TOKEN 为空时这些接口直接 403,与 ENABLE_SUBMISSION_DELETE 是两个独立的闸门。

5.4 提交缓存:只缓存查询,不去重

SUBMISSION_CACHE_DURATION 的注释容易被误读为「提交去重」。实际代码只包住了 show:

# app/controllers/submissions_controller.rb:59 (show)
render json: Rails.cache.fetch("#{token}", expires_in: Config::SUBMISSION_CACHE_DURATION, ... ) {
  Submission.find_by!(token: token)
}

实测同一份源码连续提交两次,token 不同:

t1=9d26ea41-b28e-45a4-a7e3-dabc5dde70b1
t2=61a938c5-9b38-45b1-8047-de3faf49637e
DIFFERENT

所以它的作用是「GET /submissions/{token} 的结果在 N 秒内走缓存」,降低高频轮询对数据库的压力。设为 0 可关闭缓存;批量查询接口不受该缓存影响(源码注释已说明)。

5.5 执行限制的级联关系

每个提交的最终限制由「全局默认值 × 提交级覆盖 × 全局上限」三者决定:提交里的 cpu_time_limit、memory_limit 等字段可以覆盖默认值,但不能超过 MAX_*。实测超上限直接被模型校验拦下:

$ curl -s -w " [%{http_code}]" -X POST http://127.0.0.1:8138/submissions \
    -H "Content-Type: application/json" \
    -d '{"source_code":"print(1)","language_id":71,"memory_limit":600000}'
{"memory_limit":["must be less than or equal to 512000"]} [422]

返回的是 Rails 模型校验错误(422),不是执行阶段的错误码,这类问题不会进入队列。

时间限制的实际落点可用死循环验证(cpu_time_limit=1):

{
    "stdout": null,
    "time": "0.002",
    "memory": 3268,
    "message": "Time limit exceeded (wall clock)",
    "status": {"id": 5, "description": "Time Limit Exceeded"}
}

message 明确标注是墙钟超限而不是 CPU 超限,说明孤立执行环境中 CPU 时间几乎没被消耗(进程被调度器挂起),真正兜底的是 WALL_TIME_LIMIT。

内存限制的表现为「程序自己报错」而不是「状态码 6/12 类」:

{
    "stdout": null,
    "memory": 7836,
    "stderr": "Traceback (most recent call last):\n  File \"script.py\", line 1, in <module>\n    a='x'*(300*1024*1024)\nMemoryError\n",
    "message": "Exited with error status 1",
    "status": {"id": 11, "description": "Runtime Error (NZEC)"}
}

memory_limit=64000 时 Python 在分配阶段收到 MemoryError 自行退出,最终状态是 NZEC(11)而非独立的 MLE 状态(Judge0 没有 MLE 状态码)。监控上不能靠状态码区分「内存不足」与「普通运行时错误」。

5.6 回调参数

ENABLE_CALLBACKS、CALLBACKS_MAX_TRIES、CALLBACKS_TIMEOUT 由消费者侧执行。实测回调请求使用 PUT 而不是 POST,请求体是 base64_encoded=true 的完整提交 JSON:

METHOD=PUT PATH=/cb
{"stdout":"NTYK\n","time":"0.009","memory":7992,"stderr":null,"token":"823ac49f-497e-4dcb-bbc8-07a24514d6f7","compile_output":null,"message":null,"status":{"id":3,"description":"Accepted"}}

接收端只实现 do_POST 时会收到 501,重试 CALLBACKS_MAX_TRIES 次后静默放弃——日志里没有任何记录,这是回调「看起来没触发」的最常见原因。

5.7 批量与选项开关

MAX_SUBMISSION_BATCH_SIZE 的超限校验发生在控制器入口:

$ # 提交 21 条
400 {"error":"number of submissions in a batch should be less than or equal to 20"}

ENABLE_BATCHED_SUBMISSIONS=false 时 /submissions/batch 直接不可用;ENABLE_COMPILER_OPTIONS / ALLOWED_LANGUAGES_FOR_COMPILER_OPTIONS 控制编译选项白名单,ALLOWED_... 为空表示不限制语言。ENABLE_COMMAND_LINE_ARGUMENTS=false 后提交里的 command_line_arguments 会被忽略而不是报错。

六、生产配置模板

6.1 面向 8 核 / 16GB / 纯异步的模板

适用场景:中等并发的在线评测,客户端全部走异步 + 轮询或回调。

# 连接
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=<强密码>
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=judge0
POSTGRES_USER=judge0
POSTGRES_PASSWORD=<强密码>

# Rails / 服务进程
RAILS_ENV=production
RAILS_MAX_THREADS=8
RAILS_SERVER_PROCESSES=2
SECRET_KEY_BASE=<64字节以上随机串,持久化>

# 消费者
INTERVAL=0.1

# 队列
MAX_QUEUE_SIZE=200

# 执行限制(评测口径)
CPU_TIME_LIMIT=5
MAX_CPU_TIME_LIMIT=10
WALL_TIME_LIMIT=10
MAX_WALL_TIME_LIMIT=15
MEMORY_LIMIT=256000
MAX_MEMORY_LIMIT=512000
STACK_LIMIT=64000
MAX_PROCESSES_AND_OR_THREADS=60
MAX_FILE_SIZE=1024

# 接口开关
ENABLE_WAIT_RESULT=false
ENABLE_SUBMISSION_DELETE=false
ENABLE_CALLBACKS=true
CALLBACKS_MAX_TRIES=3
CALLBACKS_TIMEOUT=5
MAX_SUBMISSION_BATCH_SIZE=20
SUBMISSION_CACHE_DURATION=1

# 访问控制
AUTHN_HEADER=X-Auth-Token
AUTHN_TOKEN=<令牌>
AUTHZ_HEADER=X-Auth-User
AUTHZ_TOKEN=<令牌>
ALLOW_ORIGIN=https://judge.example.com

# 维护
MAINTENANCE_MODE=false
MAINTENANCE_MESSAGE=Judge0 maintenance in progress, please retry later.

容量估算:RAILS_SERVER_PROCESSES × RAILS_MAX_THREADS = 16 个并发请求线程,数据库连接池按 8/进程配置;消费者按 nproc/2 个 resque:work 实例即可覆盖 8 核。

6.2 面向 16 核 / 32GB / 允许同步等待的模板

在 6.1 基础上调整三处:RAILS_MAX_THREADS=12、RAILS_SERVER_PROCESSES=2、ENABLE_WAIT_RESULT=true。同步提交会占用 API 线程直至执行完成,因此 WALL_TIME_LIMIT 不宜超过 15 秒,否则单次请求可能拖到 20 秒以上。

七、常见问题

7.1 修改 judge0.conf 后不生效

配置文件只在进程启动时被读取。Puma 与 Resque 都不会热加载,需要重启对应服务;/config_info 是确认是否生效的最快方式。

7.2 配置了 AUTHN_TOKEN 但所有请求 401

AUTHN_HEADER 未设值。config/secrets.yml 直接取 ENV["AUTHN_HEADER"],裸机部署必须显式写成 X-Auth-Token,否则头名为空串,任何令牌都匹配不到。

7.3 Puma 启动失败 invalid value for Integer(): ""

config/puma.rb:1:in `Integer': invalid value for Integer(): "" (ArgumentError)

RAILS_MAX_THREADS 或 RAILS_SERVER_PROCESSES 为空。Config 模块对这两项没有兜底,必须在配置文件里写值。

7.4 消费者启动 1 秒即退出

INTERVAL 为空导致 Float("") 异常,被 rescue Exception 吞掉后正常退出(退出码 0),日志无任何报错。补上 INTERVAL=0.1 即可。

7.5 维护期返回 500 而不是 503

MAINTENANCE_MESSAGE 含中文/非 ASCII 字符,check_maintenance 渲染时抛 Encoding::UndefinedConversionError。改用 ASCII 文案。

7.6 队列明明是空却被拒绝

检查是否漏了 Resque 前缀,正确键是 resque:queue:<JUDGE0_VERSION>;同时注意 MAX_QUEUE_SIZE 的判定包含「本次批量提交的条数」,一次提交 20 条会直接吃掉 20 个配额。

7.7 AUTHZ_TOKEN 为空时受保护接口一律 403

这是设计行为:授权未配置时受保护接口不可用。需要删除提交等能力时必须同时配置 AUTHZ_TOKEN 与 ENABLE_SUBMISSION_DELETE=true。

八、参考链接