mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
judge: fix #1204
This commit is contained in:
1 parent
2ee34911b7
commit
84f8af58a1
6 files changed
+89
-75
No files matched your search
@@ -16,11 +16,13 @@ export interface CheckConfig {
|
|||||||
env?: Record<string, string>;
|
env?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PassInfo { input: CopyInFile, state?: Record<string, CopyInFile>, dispose: () => PromiseLike<any> }
|
||||||
|
|
||||||
type Checker = (config: CheckConfig) => Promise<{
|
type Checker = (config: CheckConfig) => Promise<{
|
||||||
status: number;
|
status: number;
|
||||||
score: number;
|
score: number;
|
||||||
message: string | { message: string, params?: any[] };
|
message: string | { message: string, params?: any[] };
|
||||||
nextPass?: { input: CopyInFile, state?: Record<string, CopyInFile> };
|
nextPass?: PassInfo;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
function parseDiffMsg(msg: string) {
|
function parseDiffMsg(msg: string) {
|
||||||
@@ -237,7 +239,7 @@ const checkers: Record<string, Checker> = new Proxy({
|
|||||||
},
|
},
|
||||||
|
|
||||||
async testlib(config) {
|
async testlib(config) {
|
||||||
const { stderr, status, code, fileIds } = await runQueued(`${config.execute} /w/in /w/user_out /w/answer`, {
|
const { stderr, status, code, fileIds, [Symbol.asyncDispose]: dispose } = await runQueued(`${config.execute} /w/in /w/user_out /w/answer`, {
|
||||||
copyIn: {
|
copyIn: {
|
||||||
in: config.input,
|
in: config.input,
|
||||||
user_out: config.user_stdout,
|
user_out: config.user_stdout,
|
||||||
@@ -254,6 +256,7 @@ const checkers: Record<string, Checker> = new Proxy({
|
|||||||
[STATUS.STATUS_TIME_LIMIT_EXCEEDED]: 'Checker Time Limit Exceeded',
|
[STATUS.STATUS_TIME_LIMIT_EXCEEDED]: 'Checker Time Limit Exceeded',
|
||||||
[STATUS.STATUS_MEMORY_LIMIT_EXCEEDED]: 'Checker Memory Limit Exceeded',
|
[STATUS.STATUS_MEMORY_LIMIT_EXCEEDED]: 'Checker Memory Limit Exceeded',
|
||||||
}[status];
|
}[status];
|
||||||
|
dispose();
|
||||||
return {
|
return {
|
||||||
status: STATUS.STATUS_SYSTEM_ERROR,
|
status: STATUS.STATUS_SYSTEM_ERROR,
|
||||||
score: 0,
|
score: 0,
|
||||||
@@ -261,6 +264,7 @@ const checkers: Record<string, Checker> = new Proxy({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (status === STATUS.STATUS_RUNTIME_ERROR && !stderr?.trim()) {
|
if (status === STATUS.STATUS_RUNTIME_ERROR && !stderr?.trim()) {
|
||||||
|
dispose();
|
||||||
return {
|
return {
|
||||||
status: STATUS.STATUS_SYSTEM_ERROR,
|
status: STATUS.STATUS_SYSTEM_ERROR,
|
||||||
score: 0,
|
score: 0,
|
||||||
@@ -274,15 +278,17 @@ const checkers: Record<string, Checker> = new Proxy({
|
|||||||
nextPass: {
|
nextPass: {
|
||||||
input: { fileId: fileIds['nextpass.in'] },
|
input: { fileId: fileIds['nextpass.in'] },
|
||||||
state: fileIds['state.txt'] ? { 'state.txt': { fileId: fileIds['state.txt'] } } : undefined,
|
state: fileIds['state.txt'] ? { 'state.txt': { fileId: fileIds['state.txt'] } } : undefined,
|
||||||
|
dispose,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
dispose();
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
// https://www.kattis.com/problem-package-format/spec/2023-07-draft.html#output-validator
|
// https://www.kattis.com/problem-package-format/spec/2023-07-draft.html#output-validator
|
||||||
async kattis(config) {
|
async kattis(config) {
|
||||||
const { files, fileIds, code } = await runQueued(`${config.execute} input answer_file feedback_dir`, {
|
const { files, fileIds, code, [Symbol.asyncDispose]: dispose } = await runQueued(`${config.execute} input answer_file feedback_dir`, {
|
||||||
copyIn: {
|
copyIn: {
|
||||||
input: config.input,
|
input: config.input,
|
||||||
answer_file: config.output,
|
answer_file: config.output,
|
||||||
@@ -328,10 +334,12 @@ const checkers: Record<string, Checker> = new Proxy({
|
|||||||
state: fileIds['feedback_dir/state.txt']
|
state: fileIds['feedback_dir/state.txt']
|
||||||
? { 'feedback_dir/state.txt': { fileId: fileIds['feedback_dir/state.txt'] } }
|
? { 'feedback_dir/state.txt': { fileId: fileIds['feedback_dir/state.txt'] } }
|
||||||
: undefined,
|
: undefined,
|
||||||
|
dispose,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispose();
|
||||||
return { status, score, message };
|
return { status, score, message };
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Context, ContextSubTask } from './judge/interface';
|
|||||||
interface Task {
|
interface Task {
|
||||||
compile: () => Promise<void>;
|
compile: () => Promise<void>;
|
||||||
judgeCase: (c: NormalizedCase) => (
|
judgeCase: (c: NormalizedCase) => (
|
||||||
(ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => Promise<JudgeResultBody['case']>
|
(ctx: Context, ctxSubtask: ContextSubTask) => Promise<JudgeResultBody['case']>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ function judgeSubtask(subtask: NormalizedSubtask, sid: string, judgeCase: Task['
|
|||||||
message: '',
|
message: '',
|
||||||
} : await (async () => {
|
} : await (async () => {
|
||||||
using span = ctx.startChildSpan('judge.case', { id: subtask.cases[cid].id, subtaskId: subtask.id });
|
using span = ctx.startChildSpan('judge.case', { id: subtask.cases[cid].id, subtaskId: subtask.id });
|
||||||
const r = await runner(ctx, ctxSubtask, runner);
|
const r = await runner(ctx, ctxSubtask);
|
||||||
span.setAttributes({ status: r?.status, time: r?.time, memory: r?.memory });
|
span.setAttributes({ status: r?.status, time: r?.time, memory: r?.memory });
|
||||||
return r;
|
return r;
|
||||||
})();
|
})();
|
||||||
@@ -98,7 +98,7 @@ export const runFlow = async (ctx: Context, task: Task) => {
|
|||||||
score: subtask.type === 'min' ? subtask.score : 0,
|
score: subtask.type === 'min' ? subtask.score : 0,
|
||||||
};
|
};
|
||||||
const runner = task.judgeCase(subtask.cases.find((i) => i.input.endsWith(ctx.meta.hackRejudge)));
|
const runner = task.judgeCase(subtask.cases.find((i) => i.input.endsWith(ctx.meta.hackRejudge)));
|
||||||
const res = await runner(ctx, ctxSubtask, runner);
|
const res = await runner(ctx, ctxSubtask);
|
||||||
if (res) ctx.next({ case: res });
|
if (res) ctx.next({ case: res });
|
||||||
if (res?.status !== STATUS.STATUS_ACCEPTED) {
|
if (res?.status !== STATUS.STATUS_ACCEPTED) {
|
||||||
const totalScore = Math.sum(ctx.config.subtasks.map((i) => i.score));
|
const totalScore = Math.sum(ctx.config.subtasks.map((i) => i.score));
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
import { NormalizedCase, STATUS } from '@hydrooj/common';
|
import { NormalizedCase, STATUS } from '@hydrooj/common';
|
||||||
import checkers from '../checkers';
|
import checkers, { PassInfo } from '../checkers';
|
||||||
import { runFlow } from '../flow';
|
import { runFlow } from '../flow';
|
||||||
import { runQueued } from '../sandbox';
|
import { runQueued } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { Context, ContextSubTask, MultiPassContext } from './interface';
|
import { Context, ContextSubTask } from './interface';
|
||||||
|
|
||||||
function judgeCase(c: NormalizedCase) {
|
|
||||||
const mp: MultiPassContext = { i: 0 };
|
|
||||||
return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => {
|
|
||||||
const { address_space_limit, process_limit } = ctx.session.getLang(ctx.lang);
|
|
||||||
if (ctx.config.multi_pass && !mp.i) mp.i = 1;
|
|
||||||
|
|
||||||
|
const judgeCase = (c: NormalizedCase) => async (ctx: Context, ctxSubtask: ContextSubTask) => {
|
||||||
|
const { address_space_limit, process_limit } = ctx.session.getLang(ctx.lang);
|
||||||
|
const maxPasses = ctx.config.multi_pass || 0;
|
||||||
|
let pass = ctx.config.multi_pass ? 1 : 0;
|
||||||
|
let state: PassInfo = { input: { src: c.input }, state: {}, dispose: () => Promise.resolve() };
|
||||||
|
while (true) {
|
||||||
await using res = await runQueued(
|
await using res = await runQueued(
|
||||||
ctx.execute.execute,
|
ctx.execute.execute,
|
||||||
{
|
{
|
||||||
stdin: mp.input ?? { src: c.input },
|
stdin: state.input,
|
||||||
copyIn: { ...ctx.execute.copyIn, ...mp.state },
|
copyIn: { ...ctx.execute.copyIn, ...state.state },
|
||||||
filename: ctx.config.filename,
|
filename: ctx.config.filename,
|
||||||
time: c.time,
|
time: c.time,
|
||||||
memory: c.memory,
|
memory: c.memory,
|
||||||
@@ -23,7 +24,7 @@ function judgeCase(c: NormalizedCase) {
|
|||||||
addressSpaceLimit: address_space_limit,
|
addressSpaceLimit: address_space_limit,
|
||||||
processLimit: process_limit,
|
processLimit: process_limit,
|
||||||
},
|
},
|
||||||
`judgeCase[${c.id}]${mp.i ? `{pass=${mp.i}}` : ''}<${ctx.rid}>`,
|
`judgeCase[${c.id}${pass ? `#${pass}` : ''}]<${ctx.rid}>`,
|
||||||
);
|
);
|
||||||
const {
|
const {
|
||||||
code, signalled, time, memory, fileIds,
|
code, signalled, time, memory, fileIds,
|
||||||
@@ -31,7 +32,7 @@ function judgeCase(c: NormalizedCase) {
|
|||||||
let { status } = res;
|
let { status } = res;
|
||||||
let message: any = '';
|
let message: any = '';
|
||||||
let score = 0;
|
let score = 0;
|
||||||
let nextPass: any;
|
let nextPass;
|
||||||
if (status === STATUS.STATUS_ACCEPTED) {
|
if (status === STATUS.STATUS_ACCEPTED) {
|
||||||
if (time > c.time) {
|
if (time > c.time) {
|
||||||
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||||
@@ -44,10 +45,10 @@ function judgeCase(c: NormalizedCase) {
|
|||||||
execute: ctx.checker.execute,
|
execute: ctx.checker.execute,
|
||||||
copyIn: {
|
copyIn: {
|
||||||
...ctx.checker.copyIn,
|
...ctx.checker.copyIn,
|
||||||
...mp.state ?? {},
|
...state.state,
|
||||||
},
|
},
|
||||||
code: ctx.code,
|
code: ctx.code,
|
||||||
input: mp.input ?? { src: c.input },
|
input: state.input,
|
||||||
output: { src: c.output },
|
output: { src: c.output },
|
||||||
user_stdout: fileIds.stdout ? { fileId: fileIds.stdout } : { content: '' },
|
user_stdout: fileIds.stdout ? { fileId: fileIds.stdout } : { content: '' },
|
||||||
user_stderr: fileIds.stderr ? { fileId: fileIds.stderr } : { content: '' },
|
user_stderr: fileIds.stderr ? { fileId: fileIds.stderr } : { content: '' },
|
||||||
@@ -58,35 +59,36 @@ function judgeCase(c: NormalizedCase) {
|
|||||||
HYDRO_TESTCASE: c.id.toString(),
|
HYDRO_TESTCASE: c.id.toString(),
|
||||||
HYDRO_TIME_USAGE: time.toString(),
|
HYDRO_TIME_USAGE: time.toString(),
|
||||||
HYDRO_MEMORY_USAGE: Math.floor(memory / 1024).toString(),
|
HYDRO_MEMORY_USAGE: Math.floor(memory / 1024).toString(),
|
||||||
...(mp.i ? { HYDRO_MULTI_PASS: mp.i.toString() } : {}),
|
...(pass ? { HYDRO_MULTI_PASS: pass.toString() } : {}),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
if (mp.i && typeof message === 'string') message = `${message} [Pass ${mp.i}]`;
|
|
||||||
}
|
}
|
||||||
} else if (status === STATUS.STATUS_RUNTIME_ERROR && code && ctx.config.detail === 'full') {
|
} else if (status === STATUS.STATUS_RUNTIME_ERROR && code && ctx.config.detail === 'full') {
|
||||||
if (code < 32 && signalled) message = signals[code];
|
if (code < 32 && signalled) message = signals[code];
|
||||||
else message = { message: 'Your program returned {0}.', params: [`${code}${mp.i ? ` [Pass ${mp.i}]` : ''}`] };
|
else message = { message: 'Your program returned {0}.', params: [`${code}${pass ? ` [Pass ${pass}]` : ''}`] };
|
||||||
if (mp.i && typeof message === 'string') message = `${message} [Pass ${mp.i}]`;
|
|
||||||
}
|
}
|
||||||
|
if (pass && typeof message === 'string' && message) message += ` [Pass ${pass}]`;
|
||||||
if (nextPass) {
|
if (nextPass) {
|
||||||
if (mp.i < ctx.config.multi_pass) {
|
if (pass < maxPasses) {
|
||||||
mp.input = nextPass.input;
|
pass++;
|
||||||
mp.state = nextPass.state ?? undefined;
|
state.dispose();
|
||||||
mp.i++;
|
state = nextPass;
|
||||||
return await runner(ctx, ctxSubtask, runner);
|
continue;
|
||||||
}
|
}
|
||||||
|
nextPass.dispose();
|
||||||
status = STATUS.STATUS_SYSTEM_ERROR;
|
status = STATUS.STATUS_SYSTEM_ERROR;
|
||||||
score = 0;
|
score = 0;
|
||||||
message = { message: 'Exceeded maximum number of passes ({0}).', params: [ctx.config.multi_pass] };
|
message = { message: 'Exceeded maximum number of passes ({0}).', params: [ctx.config.multi_pass] };
|
||||||
}
|
}
|
||||||
if (runner && ctx.rerun && c.time <= 5000 && status === STATUS.STATUS_TIME_LIMIT_EXCEEDED) {
|
if (ctx.rerun && c.time <= 5000 && status === STATUS.STATUS_TIME_LIMIT_EXCEEDED) {
|
||||||
ctx.rerun--;
|
ctx.rerun--;
|
||||||
return await runner(ctx, ctxSubtask, runner);
|
continue;
|
||||||
}
|
}
|
||||||
if (!ctx.request.rejudged && !ctx.analysis && [STATUS.STATUS_WRONG_ANSWER, STATUS.STATUS_RUNTIME_ERROR].includes(status)) {
|
if (!ctx.request.rejudged && !ctx.analysis && !pass && [STATUS.STATUS_WRONG_ANSWER, STATUS.STATUS_RUNTIME_ERROR].includes(status)) {
|
||||||
ctx.analysis = true;
|
ctx.analysis = true;
|
||||||
await ctx.runAnalysis(ctx.execute, { src: c.input });
|
await ctx.runAnalysis(ctx.execute, state.input);
|
||||||
}
|
}
|
||||||
|
state.dispose();
|
||||||
return {
|
return {
|
||||||
id: c.id,
|
id: c.id,
|
||||||
subtaskId: ctxSubtask.subtask.id,
|
subtaskId: ctxSubtask.subtask.id,
|
||||||
@@ -96,8 +98,8 @@ function judgeCase(c: NormalizedCase) {
|
|||||||
memory,
|
memory,
|
||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const judge = async (ctx: Context) => await runFlow(ctx, {
|
export const judge = async (ctx: Context) => await runFlow(ctx, {
|
||||||
compile: async () => {
|
compile: async () => {
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
import { NormalizedCase, STATUS } from '@hydrooj/common';
|
import { NormalizedCase, STATUS } from '@hydrooj/common';
|
||||||
|
import { PassInfo } from '../checkers';
|
||||||
import { runFlow } from '../flow';
|
import { runFlow } from '../flow';
|
||||||
import { runPiped } from '../sandbox';
|
import { runPiped } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { parse } from '../testlib';
|
import { parse } from '../testlib';
|
||||||
import { Context, ContextSubTask, MultiPassContext } from './interface';
|
import { Context, ContextSubTask } from './interface';
|
||||||
|
|
||||||
function judgeCase(c: NormalizedCase) {
|
|
||||||
const mp: MultiPassContext = { i: 0 };
|
|
||||||
return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => {
|
|
||||||
const { address_space_limit, process_limit } = ctx.session.getLang(ctx.lang);
|
|
||||||
if (ctx.config.multi_pass && !mp.i) mp.i = 1;
|
|
||||||
|
|
||||||
|
const judgeCase = (c: NormalizedCase) => async (ctx: Context, ctxSubtask: ContextSubTask) => {
|
||||||
|
const { address_space_limit, process_limit } = ctx.session.getLang(ctx.lang);
|
||||||
|
const maxPasses = ctx.config.multi_pass || 0;
|
||||||
|
let pass = ctx.config.multi_pass ? 1 : 0;
|
||||||
|
let state: PassInfo = { input: { src: c.input }, state: {}, dispose: () => Promise.resolve() };
|
||||||
|
while (true) {
|
||||||
const [{
|
const [{
|
||||||
code, signalled, time, memory,
|
code, signalled, time, memory,
|
||||||
}, resInteractor] = await runPiped([
|
}, resInteractor] = await runPiped([
|
||||||
{
|
{
|
||||||
execute: ctx.executeUser.execute,
|
execute: ctx.executeUser.execute,
|
||||||
copyIn: { ...ctx.executeUser.copyIn, ...mp.state },
|
copyIn: { ...ctx.executeUser.copyIn, ...state.state },
|
||||||
time: c.time,
|
time: c.time,
|
||||||
memory: c.memory,
|
memory: c.memory,
|
||||||
addressSpaceLimit: address_space_limit,
|
addressSpaceLimit: address_space_limit,
|
||||||
@@ -25,10 +27,10 @@ function judgeCase(c: NormalizedCase) {
|
|||||||
{
|
{
|
||||||
execute: `${ctx.executeInteractor.execute} /w/in /w/tout /w/out`,
|
execute: `${ctx.executeInteractor.execute} /w/in /w/tout /w/out`,
|
||||||
copyIn: {
|
copyIn: {
|
||||||
in: mp.input ?? { src: c.input },
|
in: state.input,
|
||||||
out: c.output ? { src: c.output } : { content: '' },
|
out: c.output ? { src: c.output } : { content: '' },
|
||||||
...ctx.executeInteractor.copyIn,
|
...ctx.executeInteractor.copyIn,
|
||||||
...mp.state ?? {},
|
...state.state,
|
||||||
},
|
},
|
||||||
time: c.time * 2,
|
time: c.time * 2,
|
||||||
memory: c.memory * 2,
|
memory: c.memory * 2,
|
||||||
@@ -37,17 +39,18 @@ function judgeCase(c: NormalizedCase) {
|
|||||||
env: {
|
env: {
|
||||||
...ctx.env,
|
...ctx.env,
|
||||||
HYDRO_TESTCASE: c.id.toString(),
|
HYDRO_TESTCASE: c.id.toString(),
|
||||||
...(mp.i ? { HYDRO_MULTI_PASS: mp.i.toString() } : {}),
|
...(pass ? { HYDRO_MULTI_PASS: pass.toString() } : {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
], [
|
], [
|
||||||
{ in: { index: 0, fd: 1 }, out: { index: 1, fd: 0 }, name: 'userToInteractor' },
|
{ in: { index: 0, fd: 1 }, out: { index: 1, fd: 0 }, name: 'userToInteractor' },
|
||||||
{ in: { index: 1, fd: 1 }, out: { index: 0, fd: 0 }, name: 'interactorToUser' },
|
{ in: { index: 1, fd: 1 }, out: { index: 0, fd: 0 }, name: 'interactorToUser' },
|
||||||
], undefined, `judgeCase[${c.id}]${mp.i ? `[pass=${mp.i}]` : ''}<${ctx.rid}>`);
|
], undefined, `judgeCase[${c.id}${pass ? `#${pass}` : ''}]<${ctx.rid}>`);
|
||||||
|
const dispose = resInteractor[Symbol.asyncDispose];
|
||||||
// TODO handle tout (maybe pass to checker?)
|
// TODO handle tout (maybe pass to checker?)
|
||||||
let status: number;
|
let status: number;
|
||||||
let score = 0;
|
let score = 0;
|
||||||
let message: any = mp.i ? `[Pass ${mp.i}] ` : '';
|
let message: any = pass ? `[Pass ${pass}] ` : '';
|
||||||
if (time > c.time) {
|
if (time > c.time) {
|
||||||
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||||
} else if (memory > c.memory * 1024) {
|
} else if (memory > c.memory * 1024) {
|
||||||
@@ -61,21 +64,27 @@ function judgeCase(c: NormalizedCase) {
|
|||||||
status = result.status;
|
status = result.status;
|
||||||
score = result.score;
|
score = result.score;
|
||||||
message += result.message;
|
message += result.message;
|
||||||
if (resInteractor.code && !(resInteractor.stderr || '').trim().length) message += ` (Interactor exited with code ${resInteractor.code})`;
|
if (resInteractor.code && !(resInteractor.stderr || '').trim().length) {
|
||||||
if (status === STATUS.STATUS_ACCEPTED) {
|
message += ` (Interactor exited with code ${resInteractor.code})`;
|
||||||
if (resInteractor.fileIds['nextpass.in']) {
|
}
|
||||||
if (mp.i < ctx.config.multi_pass) {
|
if (status === STATUS.STATUS_ACCEPTED && resInteractor.fileIds['nextpass.in']) {
|
||||||
mp.input = { fileId: resInteractor.fileIds['nextpass.in'] };
|
if (pass < maxPasses) {
|
||||||
mp.state = resInteractor.fileIds['state.txt'] ? { 'state.txt': { fileId: resInteractor.fileIds['state.txt'] } } : undefined;
|
state.dispose();
|
||||||
mp.i++;
|
state = {
|
||||||
return await runner!(ctx, ctxSubtask, runner);
|
input: { fileId: resInteractor.fileIds['nextpass.in'] },
|
||||||
}
|
state: resInteractor.fileIds['state.txt'] ? { 'state.txt': { fileId: resInteractor.fileIds['state.txt'] } } : undefined,
|
||||||
status = STATUS.STATUS_SYSTEM_ERROR;
|
dispose,
|
||||||
score = 0;
|
};
|
||||||
message = { message: 'Exceeded maximum number of passes ({0}).', params: [ctx.config.multi_pass] };
|
pass++;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
status = STATUS.STATUS_SYSTEM_ERROR;
|
||||||
|
score = 0;
|
||||||
|
message = { message: 'Exceeded maximum number of passes ({0}).', params: [ctx.config.multi_pass] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dispose();
|
||||||
|
state.dispose();
|
||||||
return {
|
return {
|
||||||
id: c.id,
|
id: c.id,
|
||||||
subtaskId: ctxSubtask.subtask.id,
|
subtaskId: ctxSubtask.subtask.id,
|
||||||
@@ -85,8 +94,8 @@ function judgeCase(c: NormalizedCase) {
|
|||||||
memory,
|
memory,
|
||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const judge = async (ctx: Context) => await runFlow(ctx, {
|
export const judge = async (ctx: Context) => await runFlow(ctx, {
|
||||||
compile: async () => {
|
compile: async () => {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import { NormalizedSubtask } from '@hydrooj/common';
|
import { NormalizedSubtask } from '@hydrooj/common';
|
||||||
import { Execute } from '../interface';
|
import { Execute } from '../interface';
|
||||||
import { CopyInFile } from '../sandbox';
|
|
||||||
import { JudgeTask } from '../task';
|
import { JudgeTask } from '../task';
|
||||||
|
|
||||||
export type Context = JudgeTask & RuntimeContext;
|
export type Context = JudgeTask & RuntimeContext;
|
||||||
@@ -30,9 +29,3 @@ export interface ContextSubTask {
|
|||||||
score: number;
|
score: number;
|
||||||
status: number;
|
status: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MultiPassContext {
|
|
||||||
i: number;
|
|
||||||
input?: CopyInFile;
|
|
||||||
state?: Record<string, CopyInFile>;
|
|
||||||
}
|
|
||||||
@@ -47,7 +47,7 @@ export interface Parameter {
|
|||||||
filename?: string;
|
filename?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SandboxAdaptedResult {
|
interface SandboxAdaptedResult extends AsyncDisposable {
|
||||||
status: number;
|
status: number;
|
||||||
code: number;
|
code: number;
|
||||||
signalled: boolean;
|
signalled: boolean;
|
||||||
@@ -74,6 +74,10 @@ function parseArgs(execute: string): string[] {
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function del(fileId: string) {
|
||||||
|
await client.deleteFile(fileId);
|
||||||
|
}
|
||||||
|
|
||||||
function proc(params: Parameter): Cmd {
|
function proc(params: Parameter): Cmd {
|
||||||
const copyOut = supportOptional
|
const copyOut = supportOptional
|
||||||
? (params.copyOut || [])
|
? (params.copyOut || [])
|
||||||
@@ -126,6 +130,7 @@ function adaptResult(result: SandboxResult, params: Parameter): SandboxAdaptedRe
|
|||||||
memory: result.memory / 1024,
|
memory: result.memory / 1024,
|
||||||
files: result.files,
|
files: result.files,
|
||||||
code: result.exitStatus,
|
code: result.exitStatus,
|
||||||
|
[Symbol.asyncDispose]: () => Promise.allSettled([...new Set(Object.values(result.fileIds || {}))].map(del)) as Promise<any>,
|
||||||
};
|
};
|
||||||
if (ret.time > (params.time || 16000)) {
|
if (ret.time > (params.time || 16000)) {
|
||||||
ret.status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
ret.status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||||
@@ -182,10 +187,6 @@ export async function runPiped(
|
|||||||
return res.map((r) => adaptResult(r, params)) as SandboxAdaptedResult[];
|
return res.map((r) => adaptResult(r, params)) as SandboxAdaptedResult[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function del(fileId: string) {
|
|
||||||
await client.deleteFile(fileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function get(fileId: string, dest?: string) {
|
export async function get(fileId: string, dest?: string) {
|
||||||
return await client.getFile(fileId, dest);
|
return await client.getFile(fileId, dest);
|
||||||
}
|
}
|
||||||
@@ -209,8 +210,9 @@ export function runQueued(
|
|||||||
: [arg0, arg1, arg2 || {}, arg3 || '', arg4 || 0];
|
: [arg0, arg1, arg2 || {}, arg3 || '', arg4 || 0];
|
||||||
return queue.add(async () => {
|
return queue.add(async () => {
|
||||||
const res = await runPiped(execute, pipeMapping, params, trace);
|
const res = await runPiped(execute, pipeMapping, params, trace);
|
||||||
|
const disposers = res.map((t) => t[Symbol.asyncDispose]);
|
||||||
const ret = single ? res[0] : res;
|
const ret = single ? res[0] : res;
|
||||||
(ret as any)[Symbol.asyncDispose] = () => Promise.allSettled(res.flatMap((t) => Object.values(t.fileIds || {}).map(del)));
|
(ret as any)[Symbol.asyncDispose] = () => Promise.allSettled(disposers.map((dispose) => dispose()));
|
||||||
return ret;
|
return ret;
|
||||||
}, { priority });
|
}, { priority });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user