diff --git a/packages/hydrojudge/locales/zh.yaml b/packages/hydrojudge/locales/zh.yaml index 7c5c30a6e..2ad7ec6b5 100644 --- a/packages/hydrojudge/locales/zh.yaml +++ b/packages/hydrojudge/locales/zh.yaml @@ -1,6 +1,5 @@ 'Error parsing memory: {0}': 无法解析内存:{0} 'Error parsing time: {0}': 无法解析时间:{0} -Cannot connect to sandbox service.: 无法连接到沙箱服务。 Cannot find checker {0}.: 找不到比较器 {0}。 Cannot find input file {0}.: 找不到输入文件 {0}。 Cannot find judge extra file {0}.: 找不到评测额外文件 {0}。 @@ -14,6 +13,7 @@ Invalid judge_extra_files config.: 无效的 judge_extra_files 配置。 Invalid user_extra_files config.: 无效的 user_extra_files 配置。 Problem data not found.: 没有找到测试数据。 Read {0}, expect {1}.: 读取到 {0},应为 {1}。 +Sandbox Error: 沙箱错误 Standard answer longer than user output.: 标准输出比选手输出长。 Testdata configuration incorrect.: 题目配置错误。 Too many files.: 文件过多。 diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 3e80539b1..4b7365ed8 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,7 +1,7 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "2.2.3", + "version": "2.3.0", "main": "package.json", "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/hydrojudge/setting.yaml b/packages/hydrojudge/setting.yaml index ebe69dfe2..753a33a09 100644 --- a/packages/hydrojudge/setting.yaml +++ b/packages/hydrojudge/setting.yaml @@ -1,94 +1,124 @@ +sandbox_host: + type: text + name: Sandbox Host + default: http://localhost:5050 +tmp_dir: + type: text + name: Temp directory + default: $TEMP/hydro/judge.tmp +tmpfs_size: + type: text + name: Tmpfs size + default: 256m +testcases_max: + type: number + name: Max testcases per problem + default: 100 +total_time_limit: + type: number + name: Total time limit per problem + default: 60 +retry_delay_sec: + type: number + name: Retry delay sec + default: 15 +cache_dir: + type: text + name: Testdata cache directory + default: $HOME/.cache/hydro/judge +parallelism: + type: number + name: Runner parallelism + default: 2 langs: - type: textarea - category: system - desc: Language file settings - name: langs - default: | - ccWithoutO2: - type: compiler - compile: /usr/bin/g++ -Wall -o ${name} foo.cc - code_file: foo.cc - execute: ${dir}/${name} - c: - type: compiler - compile: /usr/bin/gcc -O2 -Wall -std=c99 -o ${name} foo.c -lm - code_file: foo.c - execute: ${dir}/${name} - cc: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - cc98: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - cc11: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - cc17: - type: compiler - compile: /usr/bin/g++ -O2 -Wall -std=c++17 -o ${name} foo.cc -lm - code_file: foo.cc - execute: ${dir}/${name} - pas: - type: compiler - compile: /usr/bin/fpc -O2 -o${dir}/${name} foo.pas - code_file: foo.pas - execute: ${dir}/${name} - java: - type: compiler - compile: /usr/bin/javac -d ${dir} -encoding utf8 ./Main.java - code_file: Main.java - target: Main.class - execute: /usr/bin/java Main - py: - type: compiler - compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" - code_file: foo.py - execute: /usr/bin/python ${name} - py2: - type: compiler - compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" - code_file: foo.py - execute: /usr/bin/python ${name} - py3: - type: compiler - compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" - code_file: foo.py - execute: /usr/bin/python3 ${name} - php: - type: interpreter - code_file: foo.php - execute: /usr/bin/php ${name} - rs: - type: compiler - compile: /usr/bin/rustc -O -o ${dir}/${name} ${dir}/foo.rs - code_file: foo.rs - execute: ${dir}/${name} - hs: - type: compiler - compile: /usr/bin/ghc -O -outputdir /tmp -o ${name} foo.hs - code_file: foo.hs - execute: ${dir}/${name} - js: - type: interpreter - code_file: foo.js - execute: /usr/bin/jsc ${dir}/${name} - go: - type: compiler - compile: /usr/bin/go build -o ${name} foo.go - code_file: foo.go - execute: ${dir}/${name} - rb: - type: interpreter - code_file: foo.rb - execute: /usr/bin/ruby ${name} - cs: - type: compiler - compile: /usr/bin/mcs -optimize+ -out:${dir}/${name} ${dir}/foo.cs - code_file: foo.cs - execute: /usr/bin/mono ${name} \ No newline at end of file + type: textarea + name: Language file settings + default: | + ccWithoutO2: # Do not remove this entry + type: compiler + compile: /usr/bin/g++ -Wall -o ${name} foo.cc + code_file: foo.cc + execute: ${dir}/${name} + c: + type: compiler + compile: /usr/bin/gcc -O2 -Wall -std=c99 -o ${name} foo.c -lm + code_file: foo.c + execute: ${dir}/${name} + cc: + type: compiler + compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm + code_file: foo.cc + execute: ${dir}/${name} + cc98: + type: compiler + compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm + code_file: foo.cc + execute: ${dir}/${name} + cc11: + type: compiler + compile: /usr/bin/g++ -O2 -Wall -std=c++11 -o ${name} foo.cc -lm + code_file: foo.cc + execute: ${dir}/${name} + cc17: + type: compiler + compile: /usr/bin/g++ -O2 -Wall -std=c++17 -o ${name} foo.cc -lm + code_file: foo.cc + execute: ${dir}/${name} + pas: + type: compiler + compile: /usr/bin/fpc -O2 -o${dir}/${name} foo.pas + code_file: foo.pas + execute: ${dir}/${name} + java: + type: compiler + compile: /usr/bin/javac -d ${dir} -encoding utf8 ./Main.java + code_file: Main.java + target: Main.class + execute: /usr/bin/java Main + py: + type: compiler + compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" + code_file: foo.py + execute: /usr/bin/python ${name} + py2: + type: compiler + compile: /usr/bin/python -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" + code_file: foo.py + execute: /usr/bin/python ${name} + py3: + type: compiler + compile: /usr/bin/python3 -c "import py_compile; py_compile.compile('${dir}/foo.py', '${dir}/${name}', doraise=True)" + code_file: foo.py + execute: /usr/bin/python3 ${name} + php: + type: interpreter + code_file: foo.php + execute: /usr/bin/php ${name} + rs: + type: compiler + compile: /usr/bin/rustc -O -o ${dir}/${name} ${dir}/foo.rs + code_file: foo.rs + execute: ${dir}/${name} + hs: + type: compiler + compile: /usr/bin/ghc -O -outputdir /tmp -o ${name} foo.hs + code_file: foo.hs + execute: ${dir}/${name} + js: + type: interpreter + code_file: foo.js + execute: /usr/bin/jsc ${dir}/${name} + go: + type: compiler + compile: /usr/bin/go build -o ${name} foo.go + code_file: foo.go + execute: ${dir}/${name} + rb: + type: interpreter + code_file: foo.rb + execute: /usr/bin/ruby ${name} + cs: + type: compiler + compile: /usr/bin/mcs -optimize+ -out:${dir}/${name} ${dir}/foo.cs + code_file: foo.cs + execute: /usr/bin/mono ${name} \ No newline at end of file diff --git a/packages/hydrojudge/src/cases.ts b/packages/hydrojudge/src/cases.ts index 622767b2e..ecb440b60 100644 --- a/packages/hydrojudge/src/cases.ts +++ b/packages/hydrojudge/src/cases.ts @@ -2,9 +2,9 @@ import fs from 'fs-extra'; import path from 'path'; import yaml from 'js-yaml'; import { Dictionary, sum } from 'lodash'; -import { argv } from 'yargs'; import { FormatError, SystemError } from './error'; import { parseTimeMS, parseMemoryMB, ensureFile } from './utils'; +import { getConfig } from './config'; interface Re0 { reg: RegExp, @@ -285,12 +285,12 @@ function convertIniConfig(ini: string) { } function isValidConfig(config) { - if (config.count > (argv.max_testcases_count as string || 100)) { + if (config.count > (getConfig('testcases_max') || 100)) { throw new FormatError('Too many testcases. Cancelled.'); } const total_time = sum(config.subtasks.map((subtask) => subtask.time_limit_ms * subtask.cases.length)); - if (total_time > (+argv.max_time_limit || 60) * 1000) { - throw new FormatError('Total time limit longer than {0}s. Cancelled.', [+argv.max_time_limit || 60]); + if (total_time > (getConfig('total_time_limit') || 60) * 1000) { + throw new FormatError('Total time limit longer than {0}s. Cancelled.', [+getConfig('total_time_limit') || 60]); } } diff --git a/packages/hydrojudge/src/compile.ts b/packages/hydrojudge/src/compile.ts index 13823033e..48a530881 100644 --- a/packages/hydrojudge/src/compile.ts +++ b/packages/hydrojudge/src/compile.ts @@ -4,13 +4,12 @@ import { run, del } from './sandbox'; import { CompileError, SystemError } from './error'; import { compilerText } from './utils'; import { Execute } from './interface'; +import { getConfig } from './config'; export = async function compile( lang: string, code: string, target: string, copyIn: any, next?: Function, ): Promise { - const LANGS = global.Hydro - ? yaml.load(global.Hydro.model.system.get('hydrojudge.langs')) - : require('./config').LANGS; + const LANGS = yaml.load(getConfig('langs')); if (!LANGS[lang]) throw new SystemError('Unsupported language {0}.', [lang]); const info = LANGS[lang]; target = info.target || target; diff --git a/packages/hydrojudge/src/config.ts b/packages/hydrojudge/src/config.ts index 955358af4..bbc0384a1 100644 --- a/packages/hydrojudge/src/config.ts +++ b/packages/hydrojudge/src/config.ts @@ -1,5 +1,4 @@ /* eslint-disable prefer-const */ -/* eslint-disable import/no-mutable-exports */ import { argv } from 'yargs'; import os from 'os'; import path from 'path'; @@ -7,17 +6,26 @@ import fs from 'fs-extra'; import yaml from 'js-yaml'; import log from './log'; -export let CONFIG_FILE = path.resolve(os.homedir(), '.config', 'hydro', 'judge.yaml'); -export let CACHE_DIR = path.resolve(os.homedir(), '.cache', 'hydro', 'judge'); -export let FILES_DIR = path.resolve(os.homedir(), '.cache', 'hydro', 'files', 'judge'); -export let RETRY_DELAY_SEC = 15; -export let TEMP_DIR = path.resolve(os.tmpdir(), 'hydro', 'judge'); -export let EXECUTION_HOST = 'http://localhost:5050'; -export let CONFIG = null; -export let LANGS = null; - +let CONFIG_FILE = path.resolve(os.homedir(), '.config', 'hydro', 'judge.yaml'); let LANGS_FILE = path.resolve(os.homedir(), '.config', 'hydro', 'langs.yaml'); +let config = { + cache_dir: path.resolve(os.homedir(), '.cache', 'hydro', 'judge'), + tmp_dir: path.resolve(os.tmpdir(), 'hydro', 'judge'), + tmpfs_size: '256m', + retry_delay_sec: 15, + sandbox_host: 'http://localhost:5050', + testcases_max: 100, + total_time_limit: 60, + parallelism: 2, + config: null, + langs: null, +}; + +export function getConfig(key: string) { + return global.Hydro ? global.Hydro.model.system.get(`hydrojudge.${key}`) : config[key]; +} + if (fs.existsSync(path.resolve(process.cwd(), '.env'))) { const env = {}; const f = fs.readFileSync('.env').toString(); @@ -28,34 +36,30 @@ if (fs.existsSync(path.resolve(process.cwd(), '.env'))) { Object.assign(process.env, env); } -if (process.env.CONFIG_FILE || argv.config) { - CONFIG_FILE = path.resolve(process.env.CONFIG_FILE || argv.config as string); -} -if (process.env.LANGS_FILE || argv.langs) { - LANGS_FILE = path.resolve(process.env.LANGS_FILE || argv.langs as string); -} -if (process.env.TEMP_DIR || argv.tmp) { - TEMP_DIR = path.resolve(process.env.TEMP_DIR || argv.tmp as string); -} -if (process.env.CACHE_DIR || argv.cache) { - CACHE_DIR = path.resolve(process.env.CACHE_DIR || argv.cache as string); -} -if (process.env.FILES_DIR || argv.files) { - FILES_DIR = path.resolve(process.env.FILES_DIR || argv.files as string); -} -if (process.env.EXECUTION_HOST || argv.execute) { - EXECUTION_HOST = path.resolve(process.env.EXECUTION_HOST || argv.execute as string); -} if (!global.Hydro) { - if (fs.existsSync(LANGS_FILE)) LANGS = yaml.load(fs.readFileSync(LANGS_FILE).toString()); + // standalone + if (process.env.CONFIG_FILE || argv.config) { + CONFIG_FILE = path.resolve(process.env.CONFIG_FILE || argv.config as string); + } + if (process.env.LANGS_FILE || argv.langs) { + LANGS_FILE = path.resolve(process.env.LANGS_FILE || argv.langs as string); + } + if (process.env.TEMP_DIR || argv.tmp) { + config.tmp_dir = path.resolve(process.env.TEMP_DIR || argv.tmp as string); + } + if (process.env.CACHE_DIR || argv.cache) { + config.cache_dir = path.resolve(process.env.CACHE_DIR || argv.cache as string); + } + if (process.env.EXECUTION_HOST || argv.sandbox) { + config.sandbox_host = path.resolve(process.env.EXECUTION_HOST || argv.execute as string); + } + const configFile = fs.readFileSync(CONFIG_FILE).toString(); + config = { ...config, ...yaml.load(configFile) as any }; + if (fs.existsSync(LANGS_FILE)) config.langs = yaml.load(fs.readFileSync(LANGS_FILE).toString()); else { log.error('Language file not found, using default.'); const file = path.join(path.dirname(require.resolve('@hydrooj/hydrojudge')), 'setting.yaml'); const content = yaml.load(fs.readFileSync(file).toString()) as any; - LANGS = yaml.load(content.langs.default) as any; - } - if (!(fs.existsSync(LANGS_FILE) || global.Hydro)) { - fs.ensureDirSync(path.dirname(LANGS_FILE)); - LANGS_FILE = path.join(__dirname, '..', 'examples', 'langs.yaml'); + config.langs = yaml.load(content.langs.default) as any; } } diff --git a/packages/hydrojudge/src/daemon.ts b/packages/hydrojudge/src/daemon.ts index e93d58fcf..8266f6f1a 100644 --- a/packages/hydrojudge/src/daemon.ts +++ b/packages/hydrojudge/src/daemon.ts @@ -12,10 +12,9 @@ `-.__ 听取人生经验。 __.-' ````- - -.......- - -''' ~ ~ */ -import fs from 'fs-extra'; -import yaml from 'js-yaml'; import * as Session from './hosts/index'; import { sleep, Queue } from './utils'; +import { getConfig } from './config'; import log from './log'; declare global { @@ -30,8 +29,6 @@ if (!global.onDestory) global.onDestory = []; if (!global.hosts) global.hosts = []; let SI = false; -const { RETRY_DELAY_SEC, CONFIG_FILE, CONFIG } = require('./config'); - const terminate = async () => { log.info('正在保存数据'); try { @@ -52,42 +49,16 @@ process.on('unhandledRejection', (reason, p) => { }); async function daemon() { - let config; - try { - if (CONFIG) config = CONFIG; - else { - config = (await fs.readFile(CONFIG_FILE)).toString(); - config = yaml.load(config); - } - } catch (e) { - log.error('配置文件无效或未找到。'); - process.exit(1); - } + const _hosts = getConfig('hosts'); + const retry_delay_sec = getConfig('retry_delay_sec'); const hosts = {}; const queue = new Queue(); - for (const i in config.hosts) { - config.hosts[i].host = i; - hosts[i] = new Session[config.hosts[i].type || 'vj4'](config.hosts[i]); + for (const i in _hosts) { + _hosts[i].host = i; + hosts[i] = new Session[_hosts[i].type || 'vj4'](_hosts[i]); await hosts[i].init(); } global.hosts = hosts; - if (!CONFIG) { - global.onDestory.push(async () => { - const cfg = { hosts: {} }; - for (const i in hosts) { - cfg.hosts[i] = { - host: i, - type: hosts[i].config.type || 'default', - uname: hosts[i].config.uname, - password: hosts[i].config.password, - server_url: hosts[i].config.server_url, - }; - if (hosts[i].config.cookie) cfg.hosts[i].cookie = hosts[i].config.cookie; - if (hosts[i].config.detail) cfg.hosts[i].detail = hosts[i].config.detail; - } - await fs.writeFile(CONFIG_FILE, yaml.dump(cfg)); - }); - } while ('Orz twd2') { try { for (const i in hosts) await hosts[i].consume(queue); @@ -97,11 +68,11 @@ async function daemon() { } } catch (e) { log.error(e, e.stack); - log.info(`在 ${RETRY_DELAY_SEC} 秒后重试`); - await sleep(RETRY_DELAY_SEC * 1000); + log.info(`在 ${retry_delay_sec} 秒后重试`); + await sleep(retry_delay_sec * 1000); } } } -if (!module.parent) daemon(); +if (require.main === module) daemon(); export = daemon; diff --git a/packages/hydrojudge/src/error.ts b/packages/hydrojudge/src/error.ts index 4877e91aa..36f59f038 100644 --- a/packages/hydrojudge/src/error.ts +++ b/packages/hydrojudge/src/error.ts @@ -1,15 +1,13 @@ +import { CompileErrorInfo } from './interface'; import { STATUS_TEXTS } from './status'; export class CompileError extends Error { stdout: string; - stderr: string; - status: string; - type: string; - constructor(obj) { + constructor(obj: string | CompileErrorInfo) { super('Compile Error'); if (typeof obj === 'string') { this.stdout = obj; @@ -25,10 +23,9 @@ export class CompileError extends Error { export class FormatError extends Error { type: string; - params: any[]; - constructor(message, params = []) { + constructor(message: string, params = []) { super(message); this.type = 'FormatError'; this.params = params; @@ -37,10 +34,9 @@ export class FormatError extends Error { export class RuntimeError extends Error { type: string; - detail: any; - constructor(detail, message) { + constructor(detail: string, message: string) { super(message); this.type = 'RuntimeError'; this.detail = detail; @@ -49,7 +45,6 @@ export class RuntimeError extends Error { export class SystemError extends Error { type: string; - params: any[]; constructor(message: string, params = []) { diff --git a/packages/hydrojudge/src/hosts/hydro.ts b/packages/hydrojudge/src/hosts/hydro.ts index 69c778337..608c1d4a7 100644 --- a/packages/hydrojudge/src/hosts/hydro.ts +++ b/packages/hydrojudge/src/hosts/hydro.ts @@ -9,7 +9,7 @@ import { ObjectID } from 'bson'; import * as tmpfs from '../tmpfs'; import log from '../log'; import { compilerText, Queue } from '../utils'; -import { CACHE_DIR, TEMP_DIR } from '../config'; +import { getConfig } from '../config'; import { FormatError, CompileError, SystemError } from '../error'; import { STATUS_COMPILE_ERROR, STATUS_SYSTEM_ERROR } from '../status'; import readCases from '../cases'; @@ -17,39 +17,22 @@ import judge from '../judge'; class JudgeTask { stat: Record; - session: any; - host: string; - request: any; - ws: WebSocket; - tag: any; - type: any; - domainId: string; - pid: string; - rid: string; - lang: string; - code: string; - tmpdir: string; - clean: Function[]; - folder: string; - config: any; - nextId = 1; - nextWaiting = []; constructor(session: Hydro, request, ws: WebSocket) { @@ -73,7 +56,7 @@ class JudgeTask { this.lang = this.request.lang; this.code = this.request.code; this.config = this.request.config; - this.tmpdir = path.resolve(TEMP_DIR, 'tmp', this.host, this.rid); + this.tmpdir = path.resolve(getConfig('tmp_dir'), this.host, this.rid); this.clean = []; fs.ensureDirSync(this.tmpdir); tmpfs.mount(this.tmpdir, '512m'); @@ -206,7 +189,7 @@ export default class Hydro { } async cacheOpen(domainId: string, pid: string, files: any[], next?) { - const domainDir = path.join(CACHE_DIR, this.config.host, domainId); + const domainDir = path.join(getConfig('cache_dir'), this.config.host, domainId); const filePath = path.join(domainDir, pid); await fs.ensureDir(filePath); if (!files.length) throw new SystemError('Problem data not found.'); let etags: Record; diff --git a/packages/hydrojudge/src/hosts/vj4.ts b/packages/hydrojudge/src/hosts/vj4.ts index 9ddc2e61f..ebf2f0692 100644 --- a/packages/hydrojudge/src/hosts/vj4.ts +++ b/packages/hydrojudge/src/hosts/vj4.ts @@ -8,7 +8,7 @@ import { noop } from 'lodash'; import * as tmpfs from '../tmpfs'; import log from '../log'; import { compilerText, Queue } from '../utils'; -import { CACHE_DIR, TEMP_DIR } from '../config'; +import { getConfig } from '../config'; import { FormatError, CompileError, SystemError } from '../error'; import { STATUS_COMPILE_ERROR, STATUS_SYSTEM_ERROR } from '../status'; import readCases from '../cases'; @@ -16,39 +16,22 @@ import judge from '../judge'; class JudgeTask { stat: Record; - session: any; - host: string; - request: any; - ws: WebSocket; - tag: any; - type: any; - domain_id: string; - pid: string; - rid: string; - lang: string; - code: string; - tmpdir: string; - clean: Function[]; - folder: string; - config: any; - nextId = 1; - nextWaiting = []; constructor(session: VJ4, request, ws: WebSocket) { @@ -71,7 +54,7 @@ class JudgeTask { this.rid = this.request.rid; this.lang = this.request.lang; this.code = this.request.code; - this.tmpdir = path.resolve(TEMP_DIR, 'tmp', this.host, this.rid); + this.tmpdir = path.resolve(getConfig('tmp_dir'), 'tmp', this.host, this.rid); this.clean = []; fs.ensureDirSync(this.tmpdir); tmpfs.mount(this.tmpdir, '512m'); @@ -180,9 +163,7 @@ class JudgeTask { export default class VJ4 { config: any; - axios: any; - ws: WebSocket; constructor(config) { @@ -241,7 +222,7 @@ export default class VJ4 { log.info(`Getting problem data: ${this.config.host}/${domainId}/${pid}`); await this.ensureLogin(); if (next) next({ judge_text: '正在同步测试数据,请稍后' }); - const tmpFilePath = path.resolve(CACHE_DIR, `download_${this.config.host}_${domainId}_${pid}`); + const tmpFilePath = path.resolve(getConfig('cache_dir'), `download_${this.config.host}_${domainId}_${pid}`); try { const res = await this.axios.get( `${this.config.server_url}d/${domainId}/p/${pid}/data`, @@ -274,7 +255,7 @@ export default class VJ4 { async recordPretestData(rid: string, savePath: string) { log.info(`Getting pretest data: ${this.config.host}/${rid}`); - const tmpFilePath = path.resolve(CACHE_DIR, `download_${this.config.host}_${rid}`); + const tmpFilePath = path.resolve(getConfig('cache_dir'), `download_${this.config.host}_${rid}`); await this.ensureLogin(); const res = await this.axios.get(`records/${rid}/data`, { responseType: 'stream' }); const w = fs.createWriteStream(tmpFilePath); @@ -393,7 +374,7 @@ export default class VJ4 { } async cacheOpen(domainId: string, pid: string, next) { - const domainDir = path.join(CACHE_DIR, this.config.host, domainId); + const domainDir = path.join(getConfig('cache_dir'), this.config.host, domainId); const filePath = path.join(domainDir, pid); const version = await this.problemDataVersion(domainId, pid); if (fs.existsSync(filePath)) { diff --git a/packages/hydrojudge/src/interface.ts b/packages/hydrojudge/src/interface.ts index 853d16226..00fa83ac7 100644 --- a/packages/hydrojudge/src/interface.ts +++ b/packages/hydrojudge/src/interface.ts @@ -9,3 +9,8 @@ export interface Execute { copyIn: Record, time?: number, } +export interface CompileErrorInfo { + stdout?: string, + stderr?: string, + status?: string +} diff --git a/packages/hydrojudge/src/judge/default.ts b/packages/hydrojudge/src/judge/default.ts index f4f72baea..8628b870b 100644 --- a/packages/hydrojudge/src/judge/default.ts +++ b/packages/hydrojudge/src/judge/default.ts @@ -9,6 +9,7 @@ import { run } from '../sandbox'; import compile from '../compile'; import signals from '../signals'; import { check, compileChecker } from '../check'; +import { getConfig } from '../config'; const Score = { sum: (a: number, b: number) => (a + b), @@ -158,7 +159,7 @@ export const judge = async (ctx) => { ctx.total_score = 0; ctx.total_memory_usage_kb = 0; ctx.total_time_usage_ms = 0; - ctx.queue = new Queue({ concurrency: ctx.config.concurrency || 2 }); + ctx.queue = new Queue({ concurrency: getConfig('parallelism') }); ctx.failed = {}; for (const sid in ctx.config.subtasks) tasks.push(judgeSubtask(ctx.config.subtasks[sid], sid)(ctx)); const scores = await Promise.all(tasks); diff --git a/packages/hydrojudge/src/judge/interactive.ts b/packages/hydrojudge/src/judge/interactive.ts index 3a7df1037..221a2faa1 100644 --- a/packages/hydrojudge/src/judge/interactive.ts +++ b/packages/hydrojudge/src/judge/interactive.ts @@ -5,6 +5,7 @@ import { parseFilename } from '../utils'; import { run } from '../sandbox'; import compile from '../compile'; import signals from '../signals'; +import { getConfig } from '../config'; const Score = { sum: (a, b) => (a + b), @@ -39,7 +40,7 @@ function judgeCase(c) { status = STATUS.STATUS_RUNTIME_ERROR; if (code < 32) message = signals[code]; else message = { message: 'Your program returned {0}.', params: [code] }; - } else [status, score, message] = resInteractor.files.stderr.split('\n'); + } else[status, score, message] = resInteractor.files.stderr.split('\n'); ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, score); ctxSubtask.status = Math.max(ctxSubtask.status, status); ctx.total_time_usage_ms += time_usage_ms; @@ -104,7 +105,7 @@ export const judge = async (ctx) => { ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 }); const tasks = []; ctx.total_status = ctx.total_score = ctx.total_memory_usage_kb = ctx.total_time_usage_ms = 0; - ctx.queue = new Queue({ concurrency: ctx.config.concurrency || 2 }); + ctx.queue = new Queue({ concurrency: getConfig('parallelism') }); for (const sid in ctx.config.subtasks) { tasks.push(judgeSubtask(ctx.config.subtasks[sid])(ctx)); } diff --git a/packages/hydrojudge/src/log.ts b/packages/hydrojudge/src/log.ts index bb23607c3..c194cf242 100644 --- a/packages/hydrojudge/src/log.ts +++ b/packages/hydrojudge/src/log.ts @@ -31,30 +31,19 @@ const colors = [ ]; const instances: Record = {}; - type LogFunction = (format: any, ...param: any[]) => void - type LogType = 'success' | 'error' | 'info' | 'warn' | 'debug' - export interface Logger extends Record { } export class Logger { static readonly SUCCESS = 1 - static readonly ERROR = 1 - static readonly INFO = 2 - static readonly WARN = 2 - static readonly DEBUG = 3 - static baseLevel = argv.debug ? 3 : 2 - static showDiff = false - static levels: Record = {} - static lastTime = 0 static options: InspectOptions = { @@ -73,9 +62,7 @@ export class Logger { } private code: number - private displayName: string - public stream: NodeJS.WritableStream = process.stderr constructor(public name: string, private showDiff = false) { diff --git a/packages/hydrojudge/src/sandbox.ts b/packages/hydrojudge/src/sandbox.ts index 908a59bdc..b5d2560e9 100644 --- a/packages/hydrojudge/src/sandbox.ts +++ b/packages/hydrojudge/src/sandbox.ts @@ -66,7 +66,7 @@ export async function runMultiple(execute) { body.cmd[1].files[1] = null; res = await axios.post('/run', body); } catch (e) { - throw new SystemError('Cannot connect to sandbox service.'); + throw new SystemError('Sandbox Error'); } return res.data; } @@ -87,7 +87,7 @@ export async function run(execute, params?) { [result] = res.data; } catch (e) { // FIXME request body larger than maxBodyLength limit - throw new SystemError('Cannot connect to sandbox service.', e.message); + throw new SystemError('Sandbox Error', e.message); } // FIXME: Signalled? const ret: any = { diff --git a/packages/hydrojudge/src/service.ts b/packages/hydrojudge/src/service.ts index d2f633913..8a870f366 100644 --- a/packages/hydrojudge/src/service.ts +++ b/packages/hydrojudge/src/service.ts @@ -5,7 +5,6 @@ import path from 'path'; import { argv } from 'yargs'; import { ObjectID } from 'bson'; import fs from 'fs-extra'; -import { homedir, tmpdir } from 'os'; import { noop } from 'lodash'; import { Logger } from 'hydrooj/dist/logger'; import * as monitor from 'hydrooj/dist/service/monitor'; @@ -14,10 +13,19 @@ import { FormatError, CompileError, SystemError } from './error'; import { STATUS_COMPILE_ERROR, STATUS_SYSTEM_ERROR } from './status'; import { compilerText } from './utils'; import readYamlCases from './cases'; +import { getConfig } from './config'; declare module 'hydrooj/dist/interface' { interface SystemKeys { 'hydrojudge.langs': string, + 'hydrojudge.cache_dir': string, + 'hydrojudge.tmp_dir': string, + 'hydrojudge.tmpfs_size': string, + 'hydrojudge.retry_delay_sec': number, + 'hydrojudge.sandbox_host': string, + 'hydrojudge.testcases_max': number, + 'hydrojudge.total_time_limit': number, + 'hydrojudge.parallelism': number, } } @@ -63,7 +71,7 @@ async function postInit() { } async function cacheOpen(domainId: string, pid: string, files: any[]) { - const filePath = path.join(homedir(), '.cache', 'hydro', 'judge', domainId, pid); + const filePath = path.join(getConfig('cache_dir'), domainId, pid); await fs.ensureDir(filePath); if (!files.length) throw new SystemError('Problem data not found.'); let etags: Record = {}; @@ -172,10 +180,10 @@ async function postInit() { this.config = this.request.config; this.next = getNext(this); this.end = getEnd(this.domainId, this.rid); - this.tmpdir = path.resolve(tmpdir(), 'tmp', this.rid); + this.tmpdir = path.resolve(getConfig('tmp_dir'), this.rid); this.clean = []; fs.ensureDirSync(this.tmpdir); - tmpfs.mount(this.tmpdir, '512m'); + tmpfs.mount(this.tmpdir, getConfig('tmpfs_size')); logger.info(`Submission: ${this.rid}`, { pid: this.pid }); if (this.config.input) await this.run(); else if (this.config.hack) await this.hack(); diff --git a/packages/hydrojudge/src/sysinfo.ts b/packages/hydrojudge/src/sysinfo.ts index aa32092b0..b75bedf0d 100644 --- a/packages/hydrojudge/src/sysinfo.ts +++ b/packages/hydrojudge/src/sysinfo.ts @@ -1,10 +1,10 @@ import path from 'path'; import fs from 'fs-extra'; import systeminformation from 'systeminformation'; -import { tmpdir } from 'os'; import { noop } from 'lodash'; import { judge } from './judge/run'; import * as tmpfs from './tmpfs'; +import { getConfig } from './config'; function size(s: number, base = 1) { s *= base; @@ -45,7 +45,7 @@ int main(){ }, end: () => { }, }; - context.tmpdir = path.resolve(tmpdir(), 'hydro', 'tmp', 'sysinfo'); + context.tmpdir = path.resolve(getConfig('tmp_dir'), 'sysinfo'); fs.ensureDirSync(context.tmpdir); tmpfs.mount(context.tmpdir, '32m'); await judge(context).catch((e) => console.error(e)); diff --git a/packages/hydrojudge/src/utils.ts b/packages/hydrojudge/src/utils.ts index 7994b7831..83768e018 100644 --- a/packages/hydrojudge/src/utils.ts +++ b/packages/hydrojudge/src/utils.ts @@ -30,9 +30,7 @@ export function parseMemoryMB(val: string | number) { export function sleep(timeout: number) { return new Promise((resolve) => { - setTimeout(() => { - resolve(null); - }, timeout); + setTimeout(resolve, timeout); }); } @@ -42,18 +40,11 @@ export function parseFilename(filePath: string) { } export class Queue extends EventEmitter { - queue: T[]; - - waiting: any[]; - - constructor() { - super(); - this.queue = []; - this.waiting = []; - } + queue: T[] = []; + waiting: any[] = []; get(count = 1) { - if (this.empty() || this.queue.length < count) { + if (this.queue.length < count) { return new Promise((resolve) => { this.waiting.push({ count, resolve }); }); @@ -64,10 +55,6 @@ export class Queue extends EventEmitter { return items as T[]; } - empty() { - return this.queue.length === 0; - } - push(value: T) { this.queue.push(value); if (this.waiting.length && this.waiting[0].count <= this.queue.length) { diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 1ff50e493..b8a37d1fa 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.15.27", + "version": "2.15.28", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/hydrooj/src/entry/common.ts b/packages/hydrooj/src/entry/common.ts index 269f4c027..58d3a5439 100644 --- a/packages/hydrooj/src/entry/common.ts +++ b/packages/hydrooj/src/entry/common.ts @@ -100,9 +100,9 @@ export async function setting(pending: string[], fail: string[], modelSetting: t try { const cfg: any = yaml.load(fs.readFileSync(p).toString()); for (const key in cfg) { - let val; - if (typeof cfg[key].default === 'string') { - val = cfg[key].default + let val = cfg[key].default; + if (typeof val === 'string') { + val = val .replace(/\$TEMP/g, os.tmpdir()) .replace(/\$HOME/g, os.homedir()); } diff --git a/packages/hydrooj/src/service/storage.ts b/packages/hydrooj/src/service/storage.ts index e2f7082d1..e730460fc 100644 --- a/packages/hydrooj/src/service/storage.ts +++ b/packages/hydrooj/src/service/storage.ts @@ -5,6 +5,7 @@ import { Client, BucketItem, ItemBucketMetadata } from 'minio'; import { createReadStream } from 'fs-extra'; import { Logger } from '../logger'; import { streamToBuffer } from '../utils'; +import * as system from '../model/system'; const logger = new Logger('storage'); @@ -96,10 +97,20 @@ class StorageService { assert(result.toString() === 'test'); await this.del('storage.test'); logger.success('Storage connected.'); + this.error = null; } catch (e) { - logger.error('Storage init fail.'); - logger.error(e); + logger.warn('Storage init fail. will retry later.'); this.error = e.toString(); + setTimeout(async () => { + const [endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge] = system.getMany([ + 'file.endPoint', 'file.accessKey', 'file.secretKey', 'file.bucket', 'file.region', + 'file.endPointForUser', 'file.endPointForJudge', + ]); + const sopts = { + endPoint, accessKey, secretKey, bucket, region, endPointForUser, endPointForJudge, + }; + await this.start(sopts); + }, 10000); } }