Skip to content

Judge Configuration

houyhl edited this page Oct 13, 2026 · 3 revisions

Complete judge configuration and CPU resizing

English | 简体中文

For Ubuntu/systemd deployments generated by the installer. The configuration entry point is /opt/LLMOJ/config/judge.yaml; replace the prefix when customized. This page covers every current judge YAML field and distinguishes backend-supplied limits, host environment variables and generated values.

Unchanged field sources are pinned to 43a88bdd62fbf200889b36932fa921439a293190; the modified capacity generator uses a main URL for the pending revision: config.ts, config-example.yaml, configure-judge.py, executionCapacity.ts, taskQueue.ts, aiRunner.ts.

Loading and complete field reference

LIBREOJ_JUDGE_CONFIG_FILE must name the configuration file; missing it is fatal. Required fields have no class defaults. Table examples come from example YAML, not defaults when omitted; generated installer values may differ. Placeholder key/rootfsId values are not runnable. Optional fields accept omission or null; sandbox is required and cpuAffinity is optional. Use documented fields rather than relying on rejection of unknown keys.

Field Type/validation/example Purpose, installed value and limits
serverUrl string; example http://api.libreoj.test/ Backend origin without /api; connects to namespace /judge, path /api/socket. Local installation uses http://127.0.0.1:2002; remote uses the site HTTPS origin.
downloadEndpointOverride optional string/null; example null Replaces download origin while preserving the original path/query; a path in the override is not a prefix. Local setup uses loopback Nginx; remote normally leaves it unset and must reach signed URLs.
key base64 string, exactly 40 characters Base64 representation of a 30-byte key, registered separately per judge, not a user password. Keep root:root 0600; never put it in command arguments, screenshots or public logs.
dataStore string path; example /root/judge/data Disk cache of downloaded test files by UUID; installer uses <root>/data/judge/testdata. No total cache-size setting is provided here; monitor disk usage.
binaryCacheStore string path; example /root/judge/cache Compiled binary cache; installer uses <root>/data/judge/cache. Cleared on startup; never point it at problem data, backups or another shared directory.
binaryCacheMaxSize integer bytes; example 536870912 (512 MiB) Validator requires an integer; operationally must be at least backend limit.outputSize, or the judge exits after authorization. Not a program memory limit. Installer retains the example.
taskConsumingThreads positive integer; example 2 Concurrent backend task/submission consumers, not CPU count; each task can contain many testcases. Values above 3 warn. Generated as min(3,max(1,slots//2)).
maxConcurrentDownloads positive integer; example 10 Test-file download queue concurrency; values above 20 warn. Generated as min(4,slots); increasing it does not add execution slots.
maxConcurrentTasks positive integer; example 3 Execution queue capacity, effectively the smaller of this and workspace count; shared by compilation, execution and AI validation. Generator requires equality with workspace count; exceeding OS-reported logical CPU count warns.
taskWorkingDirectories nonempty string array; example 3 paths One unique absolute directory per execution slot; duplicate resolved paths are rejected. Installer uses <root>/data/judge/work/1..N, separate tmpfs mounts, cleared on startup. Never reuse data/cache directories. A missing tmpfs may only warn; that is not a valid deployment.
rpcTimeout positive integer milliseconds; example 20000 RPC/authorization timeout; expiry restarts the process and cancels in-flight tasks. Generated value is 30000.
downloadTimeout positive integer milliseconds; example 20000 Per-attempt stream timer starts after receiving the response; not a complete DNS/connection/response-header deadline in current code. Installer retains 20000.
downloadRetry positive integer; example 3 Total attempts: 3 means first attempt plus up to 2 retries, not 3 extra retries.
aiRunnerSocket optional string; example omitted Local AI UNIX socket; worker is disabled if this or output directory is absent. All-mode path is <root>/data/judge/ai-runner.sock, root:backend-group 0660; remote uses null. Do not expose as public TCP.
aiGeneratedDirectory optional string; example omitted Private local AI output directory, <root>/data/ai-generated in all mode; real directory, not symlink, root:backend-group 0750. Shared locally with backend; null remotely.
aiRunnerGid optional integer; example omitted When AI worker is enabled, separately checked as a positive integer; all mode uses actual libreoj GID. Remote writes 0 with null socket, so disabled; do not use 0 for an enabled worker.
sandbox.rootfs string path; example /opt/rootfs-ng Sandbox root filesystem; installed at <root>/runtime/sandbox-rootfs. Preserve matching recipe, UIDs and mount layout; never recursively chown it.
sandbox.rootfsId 64 lowercase hex characters Required SHA-256 recipe ID; checked exactly against ID file at startup. Mismatch is fatal; example placeholder is not runnable.
sandbox.rootfsIdFile optional string; example omitted Defaults to <sandbox.rootfs>/etc/libreoj-rootfs-id; immutable images may use an external ID file via an absolute path.
sandbox.hostname optional string/null; example null Sandbox hostname; installer sets libreoj-judge. Not the backend connection address.
sandbox.immutable optional boolean; example omitted Absent behaves as false; true uses internal /tmp/libreoj paths and avoids creating mount directories inside the image. It does not turn an arbitrary rootfs read-only by itself; use with the matching layout.
sandbox.resourceMode optional default or arbiter; example omitted Generated as default, affecting user programs only. Default uses cgroup CPU accounting; arbiter uses process user CPU time and an additional address-space limit. Compiler/checker/interactor stay default. Changes judging semantics, not a speed toggle.
sandbox.user string; example sandbox User resolved inside rootfs to UID/GID; installer uses sandbox. Not the systemd service User; sandbox manager needs its host privileges.
sandbox.environments object of environment variables Example sets PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, HOME=/sandbox, LC_ALL=en_US.UTF-8. Use string values; merged with task-specific environment. Does not configure host Node thread pool.
cpuAffinity.compiler optional integer array; example [0,1] Logical CPU IDs allowed for compilation; installer uses selected judge CPU set.
cpuAffinity.userProgram optional integer array; example [1] Logical CPU IDs allowed for user programs; does not pin each slot exclusively to a separate CPU.
cpuAffinity.interactor optional integer array; example [0] Logical CPU IDs allowed for interactors; must fit the cgroup allowed CPU set.
cpuAffinity.checker optional integer array; example [0,1] Logical CPU IDs allowed for checkers; unset affinity adds no per-kind constraint, but systemd/parent cgroup constraints still apply.

The complete example mapping keys are sandbox.environments.PATH, sandbox.environments.HOME, and sandbox.environments.LC_ALL. sandbox.environments.* is an extensible string mapping, not another fixed field schema; example values appear above.

Host environment, queues and resource budgets

  • LIBREOJ_JUDGE_CONFIG_FILE: required path; generated service points to <root>/config/judge.yaml.
  • LIBREOJ_JUDGE_LOG_LEVEL: defaults to info; temporarily use verbose/debug for diagnosis, keeping logs with signed download URLs private.
  • UV_THREADPOOL_SIZE: set before Node starts, an integer from 2 × effective execution slots + 2 through 1024. Node's default 4 is sufficient only for 1 slot; 3 slots need at least 8, and 6 need 14. Native sandbox waits occupy libuv workers; interaction can need two child waits per slot, plus two filesystem workers. Generated value is max(4, slots*2+2). Restart judge after changes; setting this inside sandbox.environments does not work.
  • Generated NODE_OPTIONS=--max-old-space-size=512 controls Node heap, not contestant memory; PATH selects installed Node and host tools.
  • HYHOJ_AI_SAMPLE_INPUTS_DIR: private host AI sample-input directory, default /opt/LLMOJ/data/ai-sample-inputs, generated as <root>/data/ai-sample-inputs. Must agree with backend and must not be publicly served.

Ordinary backend consumers use taskConsumingThreads. Compilation, testcase execution and local AI validation share min(maxConcurrentTasks, workspace count) execution slots. Downloads have a separate maxConcurrentDownloads queue. AI runner has a fixed three concurrent generation pipelines and up to eight accepted unfinished requests, including queued work; these are not another three/eight CPU slots and have no YAML tuning keys. Backend AI concurrency is another layer. Raising consumer/AI concurrency alone does not add CPU capacity.

Backend authorization supplies limit.compilerMessage, limit.outputSize, limit.dataDisplay, limit.dataDisplayForSubmitAnswer, and limit.stderrDisplay; these are not judge.yaml fields. Compiler messages over 1 MiB, data display over 1 KiB and stderr display over 10 KiB warn. Binary cache smaller than outputSize is fatal. Problem/language time and memory limits also come from backend.

Default capacity and memory budget

Initial installation asks for judge instances (execution slots). A slot is a workspace for simultaneous compilation/testcase execution, not another OJ installation or an OS thread. One judge service uses multiple workspaces and sandbox processes. Submission consumers (taskConsumingThreads), execution slots and the Node worker pool (UV_THREADPOOL_SIZE) are separate limits.

Both all mode and remote judges use min(max(0, effective CPU count - 1), effective RAM MiB//256, 511) for the default and maximum. At least two effective CPUs and 256 MiB RAM are needed for one slot; zero capacity rejects local judging. Effective CPU capacity considers process affinity, effective cgroup cpusets and ancestor CPU quotas. Finite quota is floored with a minimum of 1; host nproc alone is insufficient. The generator selects the required IDs from the end of the allowed set, without assuming IDs start at zero or are contiguous.

Effective RAM is the smaller of host RAM and applicable cgroup memory.max. Budget 256 MiB per slot, with no additional reservation for either mode. The maximum is the minimum of effective CPUs minus one, memory slots rounded down and the libuv limit of 511. The seven-slot cap has been removed; the default is automatically capped at the allowed maximum. Explicit positive counts must also fit this maximum and cannot use the reserved CPU. For example, 512 MiB RAM permits at most two slots, provided at least three effective CPUs are available.

Effective CPUs Default slots when RAM is sufficient
1 0
2 1
3 2
4 3
8 7
16 15
32 31

With eight effective CPUs (IDs 0–7) and 8 GiB RAM, all mode defaults to CPUs 1–7, seven slots, three consumers, four concurrent downloads, workspaces 1–7 and thread pool 16. Eight CPUs with 4 GiB effective RAM still allow seven slots; with 512 MiB RAM the default and maximum are two slots, using CPUs 6–7 and thread pool 6. High-memory workloads may need a lower explicit count.

Each slot's 512 MiB tmpfs is a maximum filesystem size, not preallocated RAM or a process memory cap. Compilers, programs, page cache, AI and databases share RAM. High-memory workloads may require fewer slots. TasksMax, parent cgroups and CPU quotas still apply; 511 is not a performance or acceptance guarantee. lscpu -e=CPU,CORE,SOCKET,ONLINE distinguishes logical CPUs from physical cores. SMT siblings share a core; affinity/AllowedCPUs do not provide exclusive cores per slot.

The installer and resizer share judge_capacity.py. This new file and policy belong to the pending revision: confirm the deployed version contains them. Older pinned URLs do not represent the new implementation.

Generated systemd service and tmpfs mounts

The table covers every directive emitted by configure-judge.py and identifies common accounting/limit settings it does not emit. Static templates can differ from resource-aware generated units; review the generated files and higher-priority service drop-ins.

Directive Generated value Meaning/constraints
Unit.Description Judge: slots/consumers; workspace/remote target descriptions Descriptive text, not a resource limit.
Unit.After local: libreoj-backend.service; remote: network-online.target Startup ordering; remote target also follows network-online.target.
Unit.Requires local service: libreoj-backend.service No backend dependency on a remote node; stopping local backend affects its dependent judge.
Unit.Wants remote service: network-online.target; remote target: libreoj-judge.service Weak dependencies to activate remote networking/judge; not a multi-node scheduling rule.
Unit.PartOf service and mounts: libreoj.target or libreoj-judge.target Different all/remote stop/restart membership. Resize stops judge service, not the whole all target.
Unit.RequiresMountsFor all taskWorkingDirectories Service depends on configured workspace mounts; must match YAML.
Unit.Before not explicitly generated No explicit Before value; RequiresMountsFor and systemd default dependencies supply ordering.
Service.WorkingDirectory <root>/apps/judge Node working directory; dependencies there must remain readable.
Service.Environment NODE_ENV=production; PATH=<node-parent>:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin Also generates LIBREOJ_JUDGE_CONFIG_FILE, UV_THREADPOOL_SIZE, NODE_OPTIONS=--max-old-space-size=512, HYHOJ_AI_SAMPLE_INPUTS_DIR; see host environment section.
Service.ExecStart <node> -r @swc-node/register index.mjs Judge uses the TypeScript registration entry, not backend dist/main.js.
Service.Delegate cpu memory pids Delegates cgroup controllers to native sandbox; do not remove or replace with a sudo prefix.
Service.DelegateSubgroup supervisor Requires systemd254+; separates manager from sandbox child cgroups.
Service.OOMPolicy continue Does not stop/kill the entire service via OOMPolicy for a sandbox OOM; does not prevent host exhaustion.
Service.AllowedCPUs union of the four cpuAffinity sets Logical CPU IDs, constrained by parent cgroup; regenerate and restart.
Service.TasksMax 2048 Host service task/thread limit, not submission or execution-slot count.
Service.Restart on-failure Restarts after failure; bad configuration may repeatedly fail, requiring log inspection.
Service.RestartSec 3 Wait three seconds before restarting after failure.
Service.TimeoutStopSec 30 Thirty-second stop timeout is not queue draining and does not guarantee in-flight completion.
Service.UMask 0022 Default mask for ordinary new judge files; private config and AI paths/socket use explicit stricter permissions.
Service.LimitCORE 0 Disables core dumps. Other Limit* settings such as LimitNOFILE are not emitted and inherit host/systemd settings.
Service.StandardOutput journal Standard output goes to journal; keep diagnostic logs private.
Service.StandardError journal Standard error goes to journal.
Service.User / Service.Group not explicitly generated System service therefore uses host root by default; sandbox.user is a different inside-sandbox identity.
Service.CPUAccounting / Service.MemoryAccounting / Service.TasksAccounting not explicitly generated Inherit systemd manager/parent-slice settings; do not claim generator sets a value. Inspect effective settings with systemctl show.
Service.MemoryMax / Service.CPUQuota not explicitly generated Parent cgroup or drop-in can still limit them; per-problem sandbox limits are configured separately.
Mount.What tmpfs One independent tmpfs filesystem per workspace.
Mount.Where each taskWorkingDirectories path Unit name comes from systemd-escape --path --suffix=mount; do not guess path escaping.
Mount.Type tmpfs Not the disk testdata store; do not mount over rootfs to replace storage.
Mount.Options size=512m,mode=0755,nodev,nosuid Size is each tmpfs maximum; mode controls mount-root permissions; nodev/nosuid prevent device-node/setuid behavior.
Install.WantedBy service/mounts: mode target; remote target: multi-user.target Enable creates target .wants links; remote target needs boot enablement. Resize retains existing target enablement.

Inspect effective CPUAccounting, MemoryAccounting, and TasksAccounting read-only; resizing does not require independently changing them.

sudo systemctl show libreoj-judge.service -p CPUAccounting -p MemoryAccounting -p TasksAccounting -p LimitCORE -p TasksMax

Resize with the maintained script

Use resize-judge.sh instead of copying a long maintenance program. The new script belongs to the pending revision; install the matching update files if an older deployment lacks it. Installer options describe initial setup. Do not force resizing by deleting private YAML/keys, editing installation metadata or rerunning installation with a different --judge-slots value.

The script requires an installed libreoj-judge.service, standard work/1..N workspaces and managed service/mount units. A web-only node has no local judge to resize; see remote judges. All mode retains its local AI worker, while remote mode uses libreoj-judge.target. Default --mode auto detects the installed service; optional --mode all or --mode remote validates that selection rather than converting node roles.

The wrapper calls resize-judge.py beside it and forwards arguments without downloading a remote script. Direct main-unit MemoryMax / CPUQuota, any related drop-in, or a service bound to another prefix is rejected for manual review.

1. Review a plan

sudo bash /opt/LLMOJ/deploy/resize-judge.sh --prefix /opt/LLMOJ --plan
# Example only: the target must fit actual CPU and RAM capacity.
sudo bash /opt/LLMOJ/deploy/resize-judge.sh --prefix /opt/LLMOJ --slots 6 --plan

--plan writes no configuration and stops no services. Review old/new slots, CPU set, RAM budget and worker pool. Change --prefix for a custom installation. Drop-ins or nonstandard layouts requiring manual review are rejected rather than deleted to bypass validation.

2. Drain and apply

Schedule maintenance, prevent new submissions, rejudges, test runs and AI jobs, then wait for queued/running work from every user. Idle CPUs or your own AI list do not prove draining. --drained is operator confirmation; it does not automatically drain queues.

Default interactive mode displays capacity and the draining requirement in Chinese, then asks only for instance count. Submitting the number (or Enter for the default) confirms draining and immediately applies maintenance, with no second confirmation. Press Ctrl+C if work has not drained:

sudo bash /opt/LLMOJ/deploy/resize-judge.sh --prefix /opt/LLMOJ

After draining, a noninteractive example is:

sudo bash /opt/LLMOJ/deploy/resize-judge.sh --prefix /opt/LLMOJ --slots 6 --apply --drained

The script backs up private judge YAML, managed units and state under <prefix>/backups/judge-resize/<UTC timestamp>/, prints that private directory, then stops only the judge. It updates slots, workspaces, CPU affinity, consumer/download concurrency, worker pool, mounts and units. It preserves node key, server URL, rootfs and AI configuration. It validates units and runs a real sandbox probe. A previously running judge is restarted and its effective CPU/worker-pool settings checked; a previously stopped judge stays stopped. Verification failure triggers an attempted restore; if restoration also fails, the judge stays stopped and the backup path is reported for manual rollback. Keep the backup private because its YAML contains the node key.

3. Verify or roll back

Confirm service health and an online judge on the website, then submit ordinary and interaction/communication problems in required languages. With local AI, also check a small AI data job. A successful command is not acceptance of every workflow.

sudo systemctl is-active libreoj-judge.service
sudo systemctl show libreoj-judge.service -p AllowedCPUs -p EffectiveCPUs -p TasksMax

To roll back, block new work and drain again, replacing the path with the actual private backup printed by the script:

sudo bash /opt/LLMOJ/deploy/resize-judge.sh --prefix /opt/LLMOJ --rollback /path/to/private-backup --drained

Rollback restores saved configuration/units and recorded service/mount state, not problem/database content. It does not undo completed submissions. Verify node connection and actual submissions again after rollback.

This documents the pending revision and isolated regression scope, not a live-server resize or fresh Ubuntu 26.04 acceptance run. Related: Remote judge · Distributed judging.

Clone this wiki locally