mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 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');
|
||||
|
||||
Reference in new issue
Block a user