judge: add communication problem support (#834) (#925)

Co-authored-by: undefined <i@undefined.moe>
This commit is contained in:
Milmonandundefined authored and GitHub committed 2025-01-21 07:33:39 +08:00
1 parent abf1c7205e
commit ebc586daba
15 files changed
+151 -33

No files matched your search

+4 -1
View File
@@ -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.');
+1 -1
View File
@@ -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,
});
+2 -1
View File
@@ -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> }>;
+5 -2
View File
@@ -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>;
+14 -23
View File
@@ -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) {
+1 -1
View File
@@ -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');