mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-18 17:38:19 +08:00
Co-authored-by: undefined <i@undefined.moe>
This commit is contained in:
1 parent
abf1c7205e
commit
ebc586daba
15 files changed
+151
-33
No files matched your search
@@ -15,11 +15,14 @@ function isValidConfig(config) {
|
||||
if (config.type !== 'objective' && config.count > (getConfig('testcases_max') || 100)) {
|
||||
throw new FormatError('Too many testcases. Cancelled.');
|
||||
}
|
||||
const time = Math.sum(...config.subtasks.flatMap((subtask) => subtask.cases.map((c) => c.time)));
|
||||
const time = (config.num_processes || 1) * Math.sum(...config.subtasks.flatMap((subtask) => subtask.cases.map((c) => c.time)));
|
||||
if (time > (getConfig('total_time_limit') || 60) * 1000) {
|
||||
throw new FormatError('Total time limit longer than {0}s. Cancelled.', [+getConfig('total_time_limit') || 60]);
|
||||
}
|
||||
const memMax = Math.max(...config.subtasks.flatMap((subtask) => subtask.cases.map((c) => c.memory)));
|
||||
if (config.type === 'communication' && (config.num_processes || 2) > getConfig('processLimit')) {
|
||||
throw new FormatError('Number of processes larger than processLimit');
|
||||
}
|
||||
if (memMax > parseMemoryMB(getConfig('memoryMax'))) throw new FormatError('Memory limit larger than memory_max');
|
||||
if (!['default', 'strict'].includes(config.checker_type || 'default') && !config.checker) {
|
||||
throw new FormatError('You did not specify a checker.');
|
||||
|
||||
@@ -51,7 +51,7 @@ const testlibFile = {
|
||||
};
|
||||
|
||||
export async function compileLocalFile(
|
||||
src: string, type: 'checker' | 'validator' | 'interactor' | 'generator' | 'std',
|
||||
src: string, type: 'checker' | 'validator' | 'interactor' | 'generator' | 'manager' | 'std',
|
||||
getLang, copyIn: CopyIn, withTestlib = true, next?: any,
|
||||
) {
|
||||
const s = src.replace('@', '.').split('.');
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import { runFlow } from '../flow';
|
||||
import { Parameter, runPiped } from '../sandbox';
|
||||
import signals from '../signals';
|
||||
import { NormalizedCase } from '../utils';
|
||||
import { Context, ContextSubTask } from './interface';
|
||||
|
||||
function judgeCase(c: NormalizedCase) {
|
||||
return async (ctx: Context, ctxSubtask: ContextSubTask) => {
|
||||
const { address_space_limit, process_limit } = ctx.session.getLang(ctx.lang);
|
||||
let managerArgs = '';
|
||||
const execute: Parameter[] = [{
|
||||
execute: ctx.executeManager.execute,
|
||||
stdin: c.input ? { src: c.input } : { content: '' },
|
||||
copyIn: ctx.executeManager.copyIn,
|
||||
time: c.time * 2,
|
||||
memory: c.memory * 2,
|
||||
env: { ...ctx.env, HYDRO_TESTCASE: c.id.toString() },
|
||||
}];
|
||||
const pipeMapping = [];
|
||||
for (let i = 0; i < ctx.config.num_processes; i++) {
|
||||
managerArgs += ` /proc/self/fd/${i * 2 + 3} /proc/self/fd/${i * 2 + 4}`;
|
||||
execute.push({
|
||||
execute: `${ctx.executeUser.execute} ${i}`,
|
||||
copyIn: ctx.executeUser.copyIn,
|
||||
time: c.time,
|
||||
memory: c.memory,
|
||||
addressSpaceLimit: address_space_limit,
|
||||
processLimit: process_limit,
|
||||
});
|
||||
pipeMapping.push({
|
||||
name: `sol2mgr[${i}]`,
|
||||
in: { index: i + 1, fd: 1 },
|
||||
out: { index: 0, fd: i * 2 + 3 },
|
||||
});
|
||||
pipeMapping.push({
|
||||
name: `mgr2sol[${i}]`,
|
||||
in: { index: 0, fd: i * 2 + 4 },
|
||||
out: { index: i + 1, fd: 0 },
|
||||
});
|
||||
}
|
||||
execute[0].execute += managerArgs;
|
||||
const res = await runPiped(execute, pipeMapping);
|
||||
const resManager = res[0];
|
||||
let time = 0;
|
||||
let memory = 0;
|
||||
let score = 0;
|
||||
let status = STATUS.STATUS_ACCEPTED;
|
||||
let message: any;
|
||||
const detail = ctx.config.detail ?? true;
|
||||
for (let i = 0; i < ctx.config.num_processes; i++) {
|
||||
const result = res[i + 1];
|
||||
time += result.time;
|
||||
memory = Math.max(memory, result.memory);
|
||||
if (result.time > c.time) status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||
else if (result.memory > c.memory * 1024) status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED;
|
||||
else if ((result.code && result.code !== 13 /* Broken Pipe */) || (result.code === 13 && !resManager.code)) {
|
||||
status = STATUS.STATUS_RUNTIME_ERROR;
|
||||
if (detail) {
|
||||
if (result.code < 32 && result.signalled) message = signals[result.code];
|
||||
else message = { message: 'Your program returned {0}.', params: [result.code] };
|
||||
}
|
||||
}
|
||||
}
|
||||
if (status === STATUS.STATUS_ACCEPTED) {
|
||||
score = Math.floor(c.score * (+resManager.stdout || 0));
|
||||
status = score === c.score ? STATUS.STATUS_ACCEPTED : STATUS.STATUS_WRONG_ANSWER;
|
||||
message = resManager.stderr;
|
||||
if (resManager.code) message += ` (Manager exited with code ${resManager.code})`;
|
||||
}
|
||||
return {
|
||||
id: c.id,
|
||||
subtaskId: ctxSubtask.subtask.id,
|
||||
status,
|
||||
score,
|
||||
time,
|
||||
memory,
|
||||
message,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const judge = async (ctx: Context) => await runFlow(ctx, {
|
||||
compile: async () => {
|
||||
[ctx.executeUser, ctx.executeManager] = await Promise.all([
|
||||
ctx.compile(ctx.lang, ctx.code),
|
||||
ctx.compileLocalFile('manager', ctx.config.manager),
|
||||
]);
|
||||
},
|
||||
judgeCase,
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as communication from './communication';
|
||||
import * as def from './default';
|
||||
import * as generate from './generate';
|
||||
import * as hack from './hack';
|
||||
@@ -8,5 +9,5 @@ import * as run from './run';
|
||||
import * as submit_answer from './submit_answer';
|
||||
|
||||
export = {
|
||||
default: def, generate, interactive, run, submit_answer, objective, hack,
|
||||
default: def, generate, interactive, communication, run, submit_answer, objective, hack,
|
||||
} as Record<string, { judge(ctx: Context): Promise<void> }>;
|
||||
@@ -11,7 +11,7 @@ function judgeCase(c: NormalizedCase) {
|
||||
const { address_space_limit, process_limit } = ctx.session.getLang(ctx.lang);
|
||||
const [{
|
||||
code, signalled, time, memory,
|
||||
}, resInteractor] = await runPiped(
|
||||
}, resInteractor] = await runPiped([
|
||||
{
|
||||
execute: ctx.executeUser.execute,
|
||||
copyIn: ctx.executeUser.copyIn,
|
||||
@@ -32,7 +32,10 @@ function judgeCase(c: NormalizedCase) {
|
||||
copyOut: ['/w/tout?'],
|
||||
env: { ...ctx.env, HYDRO_TESTCASE: c.id.toString() },
|
||||
},
|
||||
);
|
||||
], [
|
||||
{ in: { index: 0, fd: 1 }, out: { index: 1, fd: 0 }, name: 'userToInteractor' },
|
||||
{ in: { index: 1, fd: 1 }, out: { index: 0, fd: 0 }, name: 'interactorToUser' },
|
||||
]);
|
||||
// TODO handle tout (maybe pass to checker?)
|
||||
let status: number;
|
||||
let score = 0;
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface RuntimeContext {
|
||||
execute?: Execute;
|
||||
checker?: Execute;
|
||||
executeInteractor?: Execute;
|
||||
executeManager?: Execute;
|
||||
executeUser?: Execute;
|
||||
|
||||
_callbackAwait?: Promise<any>;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { FormatError, SystemError } from './error';
|
||||
import { Logger } from './log';
|
||||
import client from './sandbox/client';
|
||||
import {
|
||||
Cmd, CopyIn, CopyInFile, SandboxResult, SandboxStatus,
|
||||
Cmd, CopyIn, CopyInFile, PipeMap, SandboxResult, SandboxStatus,
|
||||
} from './sandbox/interface';
|
||||
import { cmd, parseMemoryMB } from './utils';
|
||||
|
||||
@@ -29,7 +29,7 @@ const statusMap: Map<SandboxStatus, number> = new Map([
|
||||
[SandboxStatus.Signalled, STATUS.STATUS_RUNTIME_ERROR],
|
||||
]);
|
||||
|
||||
interface Parameter {
|
||||
export interface Parameter {
|
||||
time?: number;
|
||||
stdin?: CopyInFile;
|
||||
execute?: string;
|
||||
@@ -145,33 +145,24 @@ async function adaptResult(result: SandboxResult, params: Parameter): Promise<Sa
|
||||
return ret;
|
||||
}
|
||||
|
||||
export async function runPiped(execute0: Parameter, execute1: Parameter): Promise<[SandboxAdaptedResult, SandboxAdaptedResult]> {
|
||||
export async function runPiped(
|
||||
execute: Parameter[], pipeMapping: Pick<PipeMap, 'in' | 'out' | 'name'>[],
|
||||
): Promise<SandboxAdaptedResult[]> {
|
||||
let res: SandboxResult[];
|
||||
const size = parseMemoryMB(getConfig('stdio_size'));
|
||||
try {
|
||||
const body = {
|
||||
cmd: [
|
||||
proc(execute0),
|
||||
proc(execute1),
|
||||
],
|
||||
pipeMapping: [{
|
||||
in: { index: 0, fd: 1 },
|
||||
out: { index: 1, fd: 0 },
|
||||
cmd: execute.map((exe) => proc(exe)),
|
||||
pipeMapping: pipeMapping.map((pipe) => ({
|
||||
proxy: true,
|
||||
name: 'stdout',
|
||||
max: 1024 * 1024 * size,
|
||||
}, {
|
||||
in: { index: 1, fd: 1 },
|
||||
out: { index: 0, fd: 0 },
|
||||
proxy: true,
|
||||
name: 'stdout',
|
||||
max: 1024 * 1024 * size,
|
||||
}],
|
||||
...pipe,
|
||||
})),
|
||||
};
|
||||
body.cmd[0].files[0] = null;
|
||||
body.cmd[0].files[1] = null;
|
||||
body.cmd[1].files[0] = null;
|
||||
body.cmd[1].files[1] = null;
|
||||
for (let i = 0; i < body.cmd.length; i++) {
|
||||
if (pipeMapping.find((pipe) => pipe.out.index === i && pipe.out.fd === 0)) body.cmd[i].files[0] = null;
|
||||
if (pipeMapping.find((pipe) => pipe.in.index === i && pipe.in.fd === 1)) body.cmd[i].files[1] = null;
|
||||
}
|
||||
const id = callId++;
|
||||
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(body));
|
||||
res = await client.run(body);
|
||||
@@ -181,7 +172,7 @@ export async function runPiped(execute0: Parameter, execute1: Parameter): Promis
|
||||
console.error(e);
|
||||
throw new SystemError('Sandbox Error', [e]);
|
||||
}
|
||||
return await Promise.all(res.map((r) => adaptResult(r, {}))) as [SandboxAdaptedResult, SandboxAdaptedResult];
|
||||
return await Promise.all(res.map((r) => adaptResult(r, {}))) as SandboxAdaptedResult[];
|
||||
}
|
||||
|
||||
export async function del(fileId: string) {
|
||||
|
||||
@@ -135,7 +135,7 @@ export class JudgeTask {
|
||||
return result;
|
||||
}
|
||||
|
||||
async compileLocalFile(type: 'interactor' | 'validator' | 'checker' | 'generator' | 'std', file: string, checkerType?: string) {
|
||||
async compileLocalFile(type: 'interactor' | 'validator' | 'checker' | 'generator' | 'manager' | 'std', file: string, checkerType?: string) {
|
||||
if (type === 'checker' && ['default', 'strict'].includes(checkerType)) return { execute: '', copyIn: {}, clean: () => Promise.resolve(null) };
|
||||
if (type === 'checker' && !checkers[checkerType]) throw new FormatError('Unknown checker type {0}.', [checkerType]);
|
||||
const withTestlib = type !== 'std' && (type !== 'checker' || checkerType === 'testlib');
|
||||
|
||||
@@ -165,6 +165,7 @@ export enum ProblemType {
|
||||
Default = 'default',
|
||||
SubmitAnswer = 'submit_answer',
|
||||
Interactive = 'interactive',
|
||||
Communication = 'communication',
|
||||
Objective = 'objective',
|
||||
Remote = 'remote_judge',
|
||||
}
|
||||
@@ -196,6 +197,8 @@ export interface ProblemConfigFile {
|
||||
checker_type?: string;
|
||||
checker?: string;
|
||||
interactor?: string;
|
||||
manager?: string;
|
||||
num_processes?: number;
|
||||
user_extra_files?: string[];
|
||||
judge_extra_files?: string[];
|
||||
detail?: boolean;
|
||||
|
||||
@@ -52,7 +52,7 @@ const problemConfigSchema: JSONSchema7 = {
|
||||
properties: {
|
||||
redirect: { type: 'string', pattern: '[0-9a-zA-Z_-]+\\/[0-9]+' },
|
||||
key: { type: 'string', pattern: '[0-9a-f]{32}' },
|
||||
type: { enum: ['default', 'interactive', 'submit_answer', 'objective', 'remote_judge'] },
|
||||
type: { enum: ['default', 'interactive', 'communication', 'submit_answer', 'objective', 'remote_judge'] },
|
||||
subType: { type: 'string' },
|
||||
langs: { type: 'array', items: { type: 'string' } },
|
||||
target: { type: 'string' },
|
||||
@@ -64,6 +64,8 @@ const problemConfigSchema: JSONSchema7 = {
|
||||
],
|
||||
},
|
||||
interactor: { type: 'string', pattern: '\\.' },
|
||||
manager: { type: 'string', pattern: '\\.' },
|
||||
num_processes: { type: 'number', minimum: 1, maximum: 5 },
|
||||
validator: { type: 'string', pattern: '\\.' },
|
||||
user_extra_files: { type: 'array', items: { type: 'string' } },
|
||||
judge_extra_files: { type: 'array', items: { type: 'string' } },
|
||||
|
||||
@@ -29,9 +29,9 @@ interface Props {
|
||||
const configKey = [
|
||||
'type', 'subType', 'target', 'score', 'time',
|
||||
'memory', 'filename', 'checker_type', 'checker', 'interactor',
|
||||
'validator', 'user_extra_files', 'judge_extra_files', 'detail', 'outputs',
|
||||
'redirect', 'cases', 'subtasks', 'langs', 'key',
|
||||
'time_limit_rate', 'memory_limit_rate',
|
||||
'manager', 'num_processes', 'validator', 'user_extra_files', 'judge_extra_files',
|
||||
'detail', 'outputs', 'redirect', 'cases', 'subtasks',
|
||||
'langs', 'key', 'time_limit_rate', 'memory_limit_rate',
|
||||
];
|
||||
|
||||
const subtasksKey = [
|
||||
|
||||
@@ -12,6 +12,7 @@ export default function ProblemType() {
|
||||
const Type = useSelector((state: RootState) => state.config.type);
|
||||
const checkerType = useSelector((state: RootState) => state.config.checker_type);
|
||||
const filename = useSelector((state: RootState) => state.config.filename);
|
||||
const numProcesses = useSelector((state: RootState) => state.config.num_processes);
|
||||
const subType = useSelector((state: RootState) => state.config.subType);
|
||||
const checker = useSelector((state: RootState) => state.config.checker);
|
||||
const [category, setCategory] = React.useState('');
|
||||
@@ -133,6 +134,25 @@ export default function ProblemType() {
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Tab
|
||||
id="communication"
|
||||
title={i18n('problem_type.communication')}
|
||||
panel={(
|
||||
<div className="row">
|
||||
<FormItem columns={6} label="Manager">
|
||||
<SingleFileSelect formKey="manager" />
|
||||
</FormItem>
|
||||
<FormItem columns={6} label="Number of Processes">
|
||||
<input
|
||||
defaultValue={numProcesses || 2}
|
||||
placeholder="2"
|
||||
onChange={(ev) => dispatch(({ type: 'CONFIG_FORM_UPDATE', key: 'num_processes', value: +ev.currentTarget.value }))}
|
||||
className="textbox"
|
||||
/>
|
||||
</FormItem>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Tab
|
||||
id="submit_answer"
|
||||
title={i18n('problem_type.submit_answer')}
|
||||
|
||||
@@ -29,6 +29,7 @@ problem_solution: Problem Solution
|
||||
problem_submit: Problem Submit
|
||||
problem_type.default: 'Type: Default'
|
||||
problem_type.interactive: 'Type: Interactive'
|
||||
problem_type.communication: 'Type: Communication'
|
||||
problem_type.objective: 'Type: Objective'
|
||||
problem_type.remote_judge: 'Type: RemoteJudge'
|
||||
problem_type.submit_answer: 'Type: SubmitAnswer'
|
||||
|
||||
@@ -700,6 +700,7 @@ problem_solution: 题解
|
||||
problem_submit: 递交代码
|
||||
problem_type.default: 传统题
|
||||
problem_type.interactive: 交互题
|
||||
problem_type.communication: 通信题
|
||||
problem_type.objective: 客观题
|
||||
problem_type.remote_judge: 远端评测题
|
||||
problem_type.submit_answer: 提交答案题
|
||||
|
||||
@@ -19,6 +19,7 @@ export default async function readYamlCases(cfg: Record<string, any> = {}, check
|
||||
config.checker ||= checkFile(cfg.checker, 'Cannot find checker {0}.');
|
||||
}
|
||||
if (cfg.interactor) config.interactor = checkFile(cfg.interactor, 'Cannot find interactor {0}.');
|
||||
if (cfg.manager) config.manager = checkFile(cfg.manager, 'Cannot find Manager {0}.');
|
||||
if (cfg.validator) config.validator = checkFile(cfg.validator, 'Cannot find validator {0}.');
|
||||
for (const n of ['judge', 'user']) {
|
||||
const conf = cfg[`${n}_extra_files`];
|
||||
|
||||
Reference in new issue
Block a user