mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
judge: add type annotations (#300)
Co-authored-by: undefined <i@undefined.moe>
This commit is contained in:
1 parent
04685ef864
commit
b0f1c0d52c
19 files changed
+418
-71
No files matched your search
@@ -3,11 +3,24 @@ import { findFileSync } from '@hydrooj/utils/lib/utils';
|
|||||||
import checkers from './checkers';
|
import checkers from './checkers';
|
||||||
import compile from './compile';
|
import compile from './compile';
|
||||||
import { SystemError } from './error';
|
import { SystemError } from './error';
|
||||||
|
import { Execute } from './interface';
|
||||||
|
import { CopyInFile } from './sandbox/interface';
|
||||||
import { parseFilename } from './utils';
|
import { parseFilename } from './utils';
|
||||||
|
|
||||||
const testlibSrc = findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h');
|
const testlibSrc = findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h');
|
||||||
|
|
||||||
export async function check(config): Promise<[number, number, string]> {
|
interface CheckConfig {
|
||||||
|
checker_type: string;
|
||||||
|
stdin: string,
|
||||||
|
stdout: string,
|
||||||
|
user_stdout: string,
|
||||||
|
user_stderr: string,
|
||||||
|
score: number,
|
||||||
|
copyIn?: Record<string, CopyInFile>,
|
||||||
|
detail: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function check(config: CheckConfig): Promise<[number, number, string]> {
|
||||||
if (!checkers[config.checker_type]) throw new SystemError('Unknown checker type {0}', [config.checker_type]);
|
if (!checkers[config.checker_type]) throw new SystemError('Unknown checker type {0}', [config.checker_type]);
|
||||||
const {
|
const {
|
||||||
code, status, score, message,
|
code, status, score, message,
|
||||||
@@ -24,7 +37,7 @@ export async function check(config): Promise<[number, number, string]> {
|
|||||||
return [status, score, message];
|
return [status, score, message];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function compileChecker(getLang: Function, checkerType: string, checker: string, copyIn: any) {
|
export async function compileChecker(getLang: Function, checkerType: string, checker: string, copyIn: any): Promise<Execute> {
|
||||||
if (!checkers[checkerType]) throw new SystemError('Unknown checker type {0}.', [checkerType]);
|
if (!checkers[checkerType]) throw new SystemError('Unknown checker type {0}.', [checkerType]);
|
||||||
if (checkerType === 'testlib') copyIn['testlib.h'] = { src: testlibSrc };
|
if (checkerType === 'testlib') copyIn['testlib.h'] = { src: testlibSrc };
|
||||||
const file = await fs.readFile(checker);
|
const file = await fs.readFile(checker);
|
||||||
|
|||||||
@@ -2,8 +2,19 @@
|
|||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||||
import { SystemError } from './error';
|
import { SystemError } from './error';
|
||||||
import { run } from './sandbox';
|
import { run } from './sandbox';
|
||||||
|
import { CopyInFile } from './sandbox/interface';
|
||||||
import { parse } from './testlib';
|
import { parse } from './testlib';
|
||||||
|
|
||||||
|
interface CheckConfig {
|
||||||
|
input: string,
|
||||||
|
output: string,
|
||||||
|
user_stdout: string,
|
||||||
|
user_stderr: string,
|
||||||
|
score: number,
|
||||||
|
copyIn: Record<string, CopyInFile>,
|
||||||
|
detail: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
interface CheckResult {
|
interface CheckResult {
|
||||||
status: number,
|
status: number,
|
||||||
score: number,
|
score: number,
|
||||||
@@ -11,7 +22,7 @@ interface CheckResult {
|
|||||||
code?: number,
|
code?: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Checker = (config: any) => Promise<CheckResult>;
|
type Checker = (config: CheckConfig) => Promise<CheckResult>;
|
||||||
|
|
||||||
const checkers: Record<string, Checker> = {
|
const checkers: Record<string, Checker> = {
|
||||||
async default(config) {
|
async default(config) {
|
||||||
@@ -22,7 +33,7 @@ const checkers: Record<string, Checker> = {
|
|||||||
...config.copyIn,
|
...config.copyIn,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let status;
|
let status: number;
|
||||||
let message: any = '';
|
let message: any = '';
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
status = STATUS.STATUS_WRONG_ANSWER;
|
status = STATUS.STATUS_WRONG_ANSWER;
|
||||||
|
|||||||
@@ -23,19 +23,19 @@ import { Queue, sleep } from './utils';
|
|||||||
declare global {
|
declare global {
|
||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
interface Global {
|
interface Global {
|
||||||
onDestory: Function[]
|
onDestroy: Function[]
|
||||||
hosts: any
|
hosts: any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!global.onDestory) global.onDestory = [];
|
if (!global.onDestroy) global.onDestroy = [];
|
||||||
if (!global.hosts) global.hosts = [];
|
if (!global.hosts) global.hosts = [];
|
||||||
let exit = false;
|
let exit = false;
|
||||||
|
|
||||||
const terminate = async () => {
|
const terminate = async () => {
|
||||||
log.info('正在保存数据');
|
log.info('正在保存数据');
|
||||||
try {
|
try {
|
||||||
await Promise.all(global.onDestory.map((f) => f()));
|
await Promise.all(global.onDestroy.map((f: Function) => f()));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (exit) process.exit(1);
|
if (exit) process.exit(1);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class JudgeTask {
|
|||||||
this.source = this.request.source;
|
this.source = this.request.source;
|
||||||
this.tmpdir = path.resolve(getConfig('tmp_dir'), this.host, this.rid);
|
this.tmpdir = path.resolve(getConfig('tmp_dir'), this.host, this.rid);
|
||||||
this.clean = [];
|
this.clean = [];
|
||||||
await Lock.aquire(`${this.host}/${this.source}/${this.rid}`);
|
await Lock.acquire(`${this.host}/${this.source}/${this.rid}`);
|
||||||
fs.ensureDirSync(this.tmpdir);
|
fs.ensureDirSync(this.tmpdir);
|
||||||
tmpfs.mount(this.tmpdir, getConfig('tmpfs_size'));
|
tmpfs.mount(this.tmpdir, getConfig('tmpfs_size'));
|
||||||
log.info('Submission: %s/%s/%s', this.host, this.source, this.rid);
|
log.info('Submission: %s/%s/%s', this.host, this.source, this.rid);
|
||||||
@@ -170,7 +170,7 @@ export default class Hydro {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async cacheOpen(source: string, files: any[], next?) {
|
async cacheOpen(source: string, files: any[], next?) {
|
||||||
await Lock.aquire(`${this.config.host}/${source}`);
|
await Lock.acquire(`${this.config.host}/${source}`);
|
||||||
try {
|
try {
|
||||||
return this._cacheOpen(source, files, next);
|
return this._cacheOpen(source, files, next);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -245,7 +245,7 @@ export default class Hydro {
|
|||||||
this.ws = new WebSocket(`${this.config.server_url.replace(/^http/i, 'ws')}judge/conn/websocket?t=${res.data.entropy}`);
|
this.ws = new WebSocket(`${this.config.server_url.replace(/^http/i, 'ws')}judge/conn/websocket?t=${res.data.entropy}`);
|
||||||
this.ws.on('open', () => {
|
this.ws.on('open', () => {
|
||||||
this.ws.send(this.config.cookie);
|
this.ws.send(this.config.cookie);
|
||||||
global.onDestory.push(() => this.ws.close());
|
global.onDestroy.push(() => this.ws.close());
|
||||||
const content = this.config.minPriority !== undefined
|
const content = this.config.minPriority !== undefined
|
||||||
? `{"key":"prio","prio":${this.config.minPriority}}`
|
? `{"key":"prio","prio":${this.config.minPriority}}`
|
||||||
: '{"key":"ping"}';
|
: '{"key":"ping"}';
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
export interface SFile {
|
import { CopyInFile } from './sandbox/interface';
|
||||||
src?: string,
|
|
||||||
content?: string,
|
|
||||||
fileId?: string,
|
|
||||||
}
|
|
||||||
export interface Execute {
|
export interface Execute {
|
||||||
execute: string,
|
execute: string,
|
||||||
clean: Function,
|
clean: Function,
|
||||||
copyIn: Record<string, SFile>,
|
copyIn: Record<string, CopyInFile>,
|
||||||
time?: number,
|
time?: number,
|
||||||
}
|
}
|
||||||
export interface CompileErrorInfo {
|
export interface CompileErrorInfo {
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import { CompileError, FormatError } from '../error';
|
|||||||
import { run } from '../sandbox';
|
import { run } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { parseFilename } from '../utils';
|
import { parseFilename } from '../utils';
|
||||||
|
import {
|
||||||
|
Case, Context, ContextSubTask, SubTask,
|
||||||
|
} from './interface';
|
||||||
|
|
||||||
const Score = {
|
const Score = {
|
||||||
sum: (a: number, b: number) => (a + b),
|
sum: (a: number, b: number) => (a + b),
|
||||||
@@ -16,8 +19,8 @@ const Score = {
|
|||||||
min: Math.min,
|
min: Math.min,
|
||||||
};
|
};
|
||||||
|
|
||||||
function judgeCase(c, sid: string) {
|
function judgeCase(c: Case, sid: string) {
|
||||||
return async (ctx, ctxSubtask, runner?: Function) => {
|
return async (ctx: Context, ctxSubtask: ContextSubTask, runner?: Function) => {
|
||||||
if (ctx.errored || (ctx.failed[sid] && ctxSubtask.subtask.type === 'min')
|
if (ctx.errored || (ctx.failed[sid] && ctxSubtask.subtask.type === 'min')
|
||||||
|| (ctxSubtask.subtask.type === 'max' && ctxSubtask.score === ctxSubtask.subtask.score)
|
|| (ctxSubtask.subtask.type === 'max' && ctxSubtask.score === ctxSubtask.subtask.score)
|
||||||
|| ((ctxSubtask.subtask.if || []).filter((i: string) => ctx.failed[i]).length)
|
|| ((ctxSubtask.subtask.if || []).filter((i: string) => ctx.failed[i]).length)
|
||||||
@@ -72,7 +75,6 @@ function judgeCase(c, sid: string) {
|
|||||||
stdout: c.output,
|
stdout: c.output,
|
||||||
user_stdout: stdout,
|
user_stdout: stdout,
|
||||||
user_stderr: stderr,
|
user_stderr: stderr,
|
||||||
checker: ctx.config.checker,
|
|
||||||
checker_type: ctx.config.checker_type,
|
checker_type: ctx.config.checker_type,
|
||||||
score: ctxSubtask.subtask.score,
|
score: ctxSubtask.subtask.score,
|
||||||
detail: ctx.config.detail ?? true,
|
detail: ctx.config.detail ?? true,
|
||||||
@@ -109,8 +111,8 @@ function judgeCase(c, sid: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function judgeSubtask(subtask, sid: string) {
|
function judgeSubtask(subtask: SubTask, sid: string) {
|
||||||
return async (ctx) => {
|
return async (ctx: Context) => {
|
||||||
subtask.type = subtask.type || 'min';
|
subtask.type = subtask.type || 'min';
|
||||||
const ctxSubtask = {
|
const ctxSubtask = {
|
||||||
subtask,
|
subtask,
|
||||||
@@ -133,7 +135,7 @@ function judgeSubtask(subtask, sid: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const judge = async (ctx) => {
|
export const judge = async (ctx: Context) => {
|
||||||
if (!ctx.config.subtasks.length) throw new FormatError('Problem data not found.');
|
if (!ctx.config.subtasks.length) throw new FormatError('Problem data not found.');
|
||||||
ctx.next({ status: STATUS.STATUS_COMPILING });
|
ctx.next({ status: STATUS.STATUS_COMPILING });
|
||||||
if (ctx.config.template) {
|
if (ctx.config.template) {
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import { CompileError } from '../error';
|
|||||||
import { run } from '../sandbox';
|
import { run } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { copyInDir, parseFilename } from '../utils';
|
import { copyInDir, parseFilename } from '../utils';
|
||||||
|
import { Context } from './interface';
|
||||||
|
|
||||||
export const judge = async (ctx) => {
|
export const judge = async (ctx: Context) => {
|
||||||
if (ctx.config.template) {
|
if (ctx.config.template) {
|
||||||
if (ctx.config.template[ctx.lang]) {
|
if (ctx.config.template[ctx.lang]) {
|
||||||
const tpl = ctx.config.template[ctx.lang];
|
const tpl = ctx.config.template[ctx.lang];
|
||||||
@@ -100,7 +101,6 @@ export const judge = async (ctx) => {
|
|||||||
stdout,
|
stdout,
|
||||||
user_stdout: stdout,
|
user_stdout: stdout,
|
||||||
user_stderr: stderr,
|
user_stderr: stderr,
|
||||||
checker: ctx.config.checker,
|
|
||||||
checker_type: ctx.config.checker_type,
|
checker_type: ctx.config.checker_type,
|
||||||
score: 100,
|
score: 100,
|
||||||
detail: ctx.config.detail,
|
detail: ctx.config.detail,
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import * as def from './default';
|
import * as def from './default';
|
||||||
import * as hack from './hack';
|
import * as hack from './hack';
|
||||||
import * as interactive from './interactive';
|
import * as interactive from './interactive';
|
||||||
|
import { Context } from './interface';
|
||||||
import * as run from './run';
|
import * as run from './run';
|
||||||
import * as submit_answer from './submit_answer';
|
import * as submit_answer from './submit_answer';
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
default: def, interactive, run, submit_answer, hack, objective: submit_answer,
|
default: def, interactive, run, submit_answer, hack, objective: submit_answer,
|
||||||
};
|
} as Record<string, { judge(ctx: Context): Promise<void> }>;
|
||||||
@@ -7,16 +7,19 @@ import { run } from '../sandbox';
|
|||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { parse } from '../testlib';
|
import { parse } from '../testlib';
|
||||||
import { findFileSync, parseFilename } from '../utils';
|
import { findFileSync, parseFilename } from '../utils';
|
||||||
|
import {
|
||||||
|
Case, Context, ContextSubTask, SubTask,
|
||||||
|
} from './interface';
|
||||||
|
|
||||||
const testlibSrc = findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h');
|
const testlibSrc = findFileSync('@hydrooj/hydrojudge/vendor/testlib/testlib.h');
|
||||||
const Score = {
|
const Score = {
|
||||||
sum: (a, b) => (a + b),
|
sum: (a: number, b: number) => (a + b),
|
||||||
max: Math.max,
|
max: Math.max,
|
||||||
min: Math.min,
|
min: Math.min,
|
||||||
};
|
};
|
||||||
|
|
||||||
function judgeCase(c) {
|
function judgeCase(c: Case) {
|
||||||
return async (ctx, ctxSubtask) => {
|
return async (ctx: Context, ctxSubtask: ContextSubTask) => {
|
||||||
ctx.executeInteractor.copyIn.in = c.input ? { src: c.input } : { content: '' };
|
ctx.executeInteractor.copyIn.in = c.input ? { src: c.input } : { content: '' };
|
||||||
ctx.executeInteractor.copyIn.out = c.output ? { src: c.output } : { content: '' };
|
ctx.executeInteractor.copyIn.out = c.output ? { src: c.output } : { content: '' };
|
||||||
const [{ code, time_usage_ms, memory_usage_kb }, resInteractor] = await run([
|
const [{ code, time_usage_ms, memory_usage_kb }, resInteractor] = await run([
|
||||||
@@ -34,7 +37,7 @@ function judgeCase(c) {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
// TODO handle tout (maybe pass to checker?)
|
// TODO handle tout (maybe pass to checker?)
|
||||||
let status;
|
let status: number;
|
||||||
let score = 0;
|
let score = 0;
|
||||||
let message: any = '';
|
let message: any = '';
|
||||||
if (time_usage_ms > ctxSubtask.subtask.time * ctx.executeUser.time) {
|
if (time_usage_ms > ctxSubtask.subtask.time * ctx.executeUser.time) {
|
||||||
@@ -70,8 +73,8 @@ function judgeCase(c) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function judgeSubtask(subtask) {
|
function judgeSubtask(subtask: SubTask) {
|
||||||
return async (ctx) => {
|
return async (ctx: Context) => {
|
||||||
subtask.type = subtask.type || 'min';
|
subtask.type = subtask.type || 'min';
|
||||||
const ctxSubtask = {
|
const ctxSubtask = {
|
||||||
subtask,
|
subtask,
|
||||||
@@ -90,7 +93,7 @@ function judgeSubtask(subtask) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const judge = async (ctx) => {
|
export const judge = async (ctx: Context) => {
|
||||||
ctx.next({ status: STATUS.STATUS_COMPILING });
|
ctx.next({ status: STATUS.STATUS_COMPILING });
|
||||||
[ctx.executeUser, ctx.executeInteractor] = await Promise.all([
|
[ctx.executeUser, ctx.executeInteractor] = await Promise.all([
|
||||||
(() => {
|
(() => {
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import PQueue from 'p-queue';
|
||||||
|
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
||||||
|
import { Execute } from '../interface';
|
||||||
|
|
||||||
|
export type Context = JudgeTaskInterface & RuntimeContext;
|
||||||
|
|
||||||
|
export interface JudgeTaskInterface {
|
||||||
|
next(arg0: {
|
||||||
|
status?: number;
|
||||||
|
message?: string;
|
||||||
|
progress?: number;
|
||||||
|
case?: {
|
||||||
|
status: number;
|
||||||
|
score?: number;
|
||||||
|
time_ms: number;
|
||||||
|
memory_kb: number;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
}, arg1?: number): void;
|
||||||
|
end(arg0: {
|
||||||
|
status: number;
|
||||||
|
score: number;
|
||||||
|
time_ms: number;
|
||||||
|
memory_kb: number;
|
||||||
|
}): void;
|
||||||
|
getLang: (name: string) => LangConfig;
|
||||||
|
|
||||||
|
stat: Record<string, Date>;
|
||||||
|
lang: string;
|
||||||
|
code: string;
|
||||||
|
tmpdir: string;
|
||||||
|
input?: string;
|
||||||
|
clean: Function[];
|
||||||
|
config: Config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RuntimeContext {
|
||||||
|
total_score?: number;
|
||||||
|
total_status?: number;
|
||||||
|
total_time_usage_ms?: number;
|
||||||
|
total_memory_usage_kb?: number;
|
||||||
|
|
||||||
|
queue?: PQueue;
|
||||||
|
errored?: boolean;
|
||||||
|
rerun?: number;
|
||||||
|
failed?: Record<string, boolean>;
|
||||||
|
|
||||||
|
execute?: Execute;
|
||||||
|
checker?: Execute;
|
||||||
|
executeInteractor?: Execute;
|
||||||
|
executeUser?: Execute;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtraFile = string[];
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
time: string;
|
||||||
|
memory: string;
|
||||||
|
|
||||||
|
subtasks?: SubTask[];
|
||||||
|
count?: number;
|
||||||
|
checker_type?: string;
|
||||||
|
detail?: boolean;
|
||||||
|
filename?: string;
|
||||||
|
|
||||||
|
judge_extra_files?: ExtraFile;
|
||||||
|
user_extra_files?: ExtraFile;
|
||||||
|
template?: Record<string, [string, string]>;
|
||||||
|
|
||||||
|
checker?: string;
|
||||||
|
validator?: string;
|
||||||
|
std?: string;
|
||||||
|
hack?: string;
|
||||||
|
interactor?: string;
|
||||||
|
|
||||||
|
outputs?: {
|
||||||
|
output: string,
|
||||||
|
score: number,
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubTask {
|
||||||
|
time: number;
|
||||||
|
memory: number;
|
||||||
|
type: 'sum' | 'max' | 'min';
|
||||||
|
score: number;
|
||||||
|
cases: Record<string, Case>;
|
||||||
|
if: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContextSubTask {
|
||||||
|
subtask: SubTask;
|
||||||
|
score: number;
|
||||||
|
status: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Case {
|
||||||
|
id: number;
|
||||||
|
input: string;
|
||||||
|
output: string;
|
||||||
|
}
|
||||||
@@ -6,8 +6,9 @@ import { CompileError } from '../error';
|
|||||||
import { run } from '../sandbox';
|
import { run } from '../sandbox';
|
||||||
import signals from '../signals';
|
import signals from '../signals';
|
||||||
import { compilerText, parseMemoryMB, parseTimeMS } from '../utils';
|
import { compilerText, parseMemoryMB, parseTimeMS } from '../utils';
|
||||||
|
import { Context } from './interface';
|
||||||
|
|
||||||
export const judge = async (ctx) => {
|
export const judge = async (ctx: Context) => {
|
||||||
ctx.stat.judge = new Date();
|
ctx.stat.judge = new Date();
|
||||||
ctx.next({ status: STATUS.STATUS_COMPILING });
|
ctx.next({ status: STATUS.STATUS_COMPILING });
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||||
|
import { Context } from './interface';
|
||||||
|
|
||||||
export async function judge({
|
export async function judge({
|
||||||
next, end, config, code,
|
next, end, config, code,
|
||||||
}) {
|
}: Context) {
|
||||||
next({ status: STATUS.STATUS_JUDGING, progress: 0 });
|
next({ status: STATUS.STATUS_JUDGING, progress: 0 });
|
||||||
code = code.replace(/\r/g, '');
|
code = code.replace(/\r/g, '');
|
||||||
const outputs = code.split('\n');
|
const outputs = code.split('\n');
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export class Logger {
|
|||||||
private createMethod(name: LogType, prefix: string, minLevel: number) {
|
private createMethod(name: LogType, prefix: string, minLevel: number) {
|
||||||
this[name] = (...args: [any, ...any[]]) => {
|
this[name] = (...args: [any, ...any[]]) => {
|
||||||
if (this.level < minLevel) return;
|
if (this.level < minLevel) return;
|
||||||
this.stream.write(`${prefix + this.displayName + this.format(...args)}\n`);
|
this.stream.write(`${prefix} ${this.displayName} ${this.format(...args)}\n`);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import Axios from 'axios';
|
|
||||||
import cac from 'cac';
|
import cac from 'cac';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import { ParseEntry } from 'shell-quote';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||||
import { getConfig } from './config';
|
import { getConfig } from './config';
|
||||||
import { FormatError, SystemError } from './error';
|
import { FormatError, SystemError } from './error';
|
||||||
import { Logger } from './log';
|
import { Logger } from './log';
|
||||||
|
import { SandboxClient } from './sandbox/client';
|
||||||
|
import {
|
||||||
|
Cmd, CopyInFile, SandboxResult, SandboxStatus,
|
||||||
|
} from './sandbox/interface';
|
||||||
import { cmd, parseMemoryMB } from './utils';
|
import { cmd, parseMemoryMB } from './utils';
|
||||||
|
|
||||||
const argv = cac().parse();
|
const argv = cac().parse();
|
||||||
@@ -12,16 +16,41 @@ const logger = new Logger('sandbox');
|
|||||||
let callId = 0;
|
let callId = 0;
|
||||||
let supportOptional = false;
|
let supportOptional = false;
|
||||||
|
|
||||||
const statusMap = {
|
const statusMap: Map<SandboxStatus, number> = new Map([
|
||||||
'Time Limit Exceeded': STATUS.STATUS_TIME_LIMIT_EXCEEDED,
|
[SandboxStatus.TimeLimitExceeded, STATUS.STATUS_TIME_LIMIT_EXCEEDED],
|
||||||
'Memory Limit Exceeded': STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
|
[SandboxStatus.MemoryLimitExceeded, STATUS.STATUS_MEMORY_LIMIT_EXCEEDED],
|
||||||
'Output Limit Exceeded': STATUS.STATUS_RUNTIME_ERROR,
|
[SandboxStatus.OutputLimitExceeded, STATUS.STATUS_RUNTIME_ERROR],
|
||||||
Accepted: STATUS.STATUS_ACCEPTED,
|
[SandboxStatus.Accepted, STATUS.STATUS_ACCEPTED],
|
||||||
'Nonzero Exit Status': STATUS.STATUS_RUNTIME_ERROR,
|
[SandboxStatus.NonzeroExitStatus, STATUS.STATUS_RUNTIME_ERROR],
|
||||||
'Internal Error': STATUS.STATUS_SYSTEM_ERROR,
|
[SandboxStatus.InternalError, STATUS.STATUS_SYSTEM_ERROR],
|
||||||
'File Error': STATUS.STATUS_SYSTEM_ERROR,
|
[SandboxStatus.FileError, STATUS.STATUS_SYSTEM_ERROR],
|
||||||
Signalled: STATUS.STATUS_RUNTIME_ERROR,
|
[SandboxStatus.Signalled, STATUS.STATUS_RUNTIME_ERROR],
|
||||||
};
|
]);
|
||||||
|
|
||||||
|
interface Parameter {
|
||||||
|
time?: number;
|
||||||
|
stdin?: string;
|
||||||
|
stdout?: string;
|
||||||
|
stderr?: string;
|
||||||
|
execute?: string;
|
||||||
|
memory?: number;
|
||||||
|
processLimit?: number;
|
||||||
|
copyIn?: Record<string, CopyInFile>;
|
||||||
|
copyOut?: string[];
|
||||||
|
copyOutCached?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkStringArray(args: ParseEntry[]): args is string[] {
|
||||||
|
return args.every((arg: ParseEntry) => typeof arg === 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(execute: string): string[] {
|
||||||
|
const args = cmd(execute.replace(/\$\{dir\}/g, '/w'));
|
||||||
|
if (!checkStringArray(args)) {
|
||||||
|
throw new SystemError(`${execute} contains invalid operator`);
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
function proc({
|
function proc({
|
||||||
execute = '',
|
execute = '',
|
||||||
@@ -29,14 +58,14 @@ function proc({
|
|||||||
memory = parseMemoryMB(getConfig('memoryMax')),
|
memory = parseMemoryMB(getConfig('memoryMax')),
|
||||||
processLimit = getConfig('processLimit'),
|
processLimit = getConfig('processLimit'),
|
||||||
stdin = '', copyIn = {}, copyOut = [], copyOutCached = [],
|
stdin = '', copyIn = {}, copyOut = [], copyOutCached = [],
|
||||||
} = {}) {
|
}: Parameter = {}): Cmd {
|
||||||
if (!supportOptional) {
|
if (!supportOptional) {
|
||||||
copyOut = (copyOut as string[]).map((i) => (i.endsWith('?') ? i.substr(0, i.length - 1) : i));
|
copyOut = (copyOut as string[]).map((i) => (i.endsWith('?') ? i.substr(0, i.length - 1) : i));
|
||||||
}
|
}
|
||||||
const size = parseMemoryMB(getConfig('stdio_size'));
|
const size = parseMemoryMB(getConfig('stdio_size'));
|
||||||
const rate = getConfig('rate');
|
const rate = getConfig('rate');
|
||||||
return {
|
return {
|
||||||
args: cmd(execute.replace(/\$\{dir\}/g, '/w')),
|
args: parseArgs(execute),
|
||||||
env: getConfig('env').split('\n'),
|
env: getConfig('env').split('\n'),
|
||||||
files: [
|
files: [
|
||||||
stdin ? { src: stdin } : { content: '' },
|
stdin ? { src: stdin } : { content: '' },
|
||||||
@@ -44,7 +73,7 @@ function proc({
|
|||||||
{ name: 'stderr', max: Math.floor(1024 * 1024 * size) },
|
{ name: 'stderr', max: Math.floor(1024 * 1024 * size) },
|
||||||
],
|
],
|
||||||
cpuLimit: Math.floor(time * 1000 * 1000 * rate),
|
cpuLimit: Math.floor(time * 1000 * 1000 * rate),
|
||||||
realCpuLimit: Math.floor(time * 3000 * 1000 * rate),
|
clockLimit: Math.floor(time * 3000 * 1000 * rate),
|
||||||
memoryLimit: Math.floor(memory * 1024 * 1024),
|
memoryLimit: Math.floor(memory * 1024 * 1024),
|
||||||
strictMemoryLimit: getConfig('strict_memory'),
|
strictMemoryLimit: getConfig('strict_memory'),
|
||||||
// stackLimit: memory * 1024 * 1024,
|
// stackLimit: memory * 1024 * 1024,
|
||||||
@@ -55,11 +84,11 @@ function proc({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function adaptResult(result, params) {
|
async function adaptResult(result: SandboxResult, params: Parameter) {
|
||||||
const rate = getConfig('rate');
|
const rate = getConfig('rate');
|
||||||
// FIXME: Signalled?
|
// FIXME: Signalled?
|
||||||
const ret: any = {
|
const ret: any = {
|
||||||
status: statusMap[result.status] || STATUS.STATUS_ACCEPTED,
|
status: statusMap.get(result.status) || STATUS.STATUS_ACCEPTED,
|
||||||
time_usage_ms: result.time / 1000000 / rate,
|
time_usage_ms: result.time / 1000000 / rate,
|
||||||
memory_usage_kb: result.memory / 1024,
|
memory_usage_kb: result.memory / 1024,
|
||||||
files: result.files,
|
files: result.files,
|
||||||
@@ -78,8 +107,8 @@ async function adaptResult(result, params) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runMultiple(execute) {
|
export async function runMultiple(execute: Parameter[]) {
|
||||||
let res;
|
let res: SandboxResult[];
|
||||||
const size = parseMemoryMB(getConfig('stdio_size'));
|
const size = parseMemoryMB(getConfig('stdio_size'));
|
||||||
try {
|
try {
|
||||||
const body = {
|
const body = {
|
||||||
@@ -107,35 +136,35 @@ export async function runMultiple(execute) {
|
|||||||
body.cmd[1].files[1] = null;
|
body.cmd[1].files[1] = null;
|
||||||
const id = callId++;
|
const id = callId++;
|
||||||
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(body));
|
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(body));
|
||||||
res = await Axios.create({ baseURL: getConfig('sandbox_host') }).post('/run', body);
|
res = await new SandboxClient(getConfig('sandbox_host')).run(body);
|
||||||
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(res.data));
|
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(res));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof FormatError) throw e;
|
if (e instanceof FormatError) throw e;
|
||||||
throw new SystemError('Sandbox Error', [e]);
|
throw new SystemError('Sandbox Error', [e]);
|
||||||
}
|
}
|
||||||
return await Promise.all(res.data.map((i) => adaptResult(i, {})));
|
return await Promise.all(res.map((r) => adaptResult(r, {})));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function del(fileId: string) {
|
export async function del(fileId: string) {
|
||||||
const res = await Axios.create({ baseURL: getConfig('sandbox_host') }).delete(`/file/${fileId}`);
|
await new SandboxClient(getConfig('sandbox_host')).deleteFile(fileId);
|
||||||
return res.data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function run(execute, params?) {
|
export async function run(execute: string | Parameter[], params?: Parameter) {
|
||||||
let result;
|
let result: SandboxResult;
|
||||||
if (typeof execute === 'object') return await runMultiple(execute);
|
if (typeof execute === 'object') return await runMultiple(execute);
|
||||||
try {
|
try {
|
||||||
|
const client = new SandboxClient(getConfig('sandbox_host'));
|
||||||
if (!supportOptional) {
|
if (!supportOptional) {
|
||||||
const res = await Axios.create({ baseURL: getConfig('sandbox_host') }).get('/version');
|
const res = await client.version();
|
||||||
supportOptional = res.data.copyOutOptional;
|
supportOptional = res.copyOutOptional;
|
||||||
if (!supportOptional) logger.warn('Sandbox version tooooooo low! Please upgrade to at least 1.2.0');
|
if (!supportOptional) logger.warn('Sandbox version tooooooo low! Please upgrade to at least 1.2.0');
|
||||||
}
|
}
|
||||||
const body = { cmd: [proc({ execute, ...params })] };
|
const body = { cmd: [proc({ execute, ...params })] };
|
||||||
const id = callId++;
|
const id = callId++;
|
||||||
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(body));
|
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(body));
|
||||||
const res = await Axios.create({ baseURL: getConfig('sandbox_host') }).post('/run', body);
|
const res = await client.run(body);
|
||||||
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(res.data));
|
if (argv.options.showSandbox) logger.debug('%d %s', id, JSON.stringify(res));
|
||||||
[result] = res.data;
|
[result] = res;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof FormatError) throw e;
|
if (e instanceof FormatError) throw e;
|
||||||
// FIXME request body larger than maxBodyLength limit
|
// FIXME request body larger than maxBodyLength limit
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
import { SandboxRequest, SandboxResult, SandboxVersion } from './interface';
|
||||||
|
|
||||||
|
export class SandboxClient {
|
||||||
|
private client: AxiosInstance;
|
||||||
|
|
||||||
|
constructor(baseURL: string) {
|
||||||
|
this.client = axios.create({ baseURL });
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(req: SandboxRequest): Promise<SandboxResult[]> {
|
||||||
|
return this.client.post('/run', req).then((res) => res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFile(fileId: string): Promise<Buffer> {
|
||||||
|
return this.client.get(`/file/${fileId}`).then((res) => res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteFile(fileId: string): Promise<never> {
|
||||||
|
return this.client.delete(`/file/${fileId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public listFiles(): Promise<Record<string, string>> {
|
||||||
|
return this.client.get('/file').then((res) => res.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public version(): Promise<SandboxVersion> {
|
||||||
|
return this.client.get('/version').then((res) => res.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
export interface SandboxVersion {
|
||||||
|
buildVersion: string;
|
||||||
|
goVersion: string;
|
||||||
|
platform: string;
|
||||||
|
os: string;
|
||||||
|
copyOutOptional?: boolean;
|
||||||
|
pipeProxy?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocalFile {
|
||||||
|
src: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MemoryFile {
|
||||||
|
content: string | Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PreparedFile {
|
||||||
|
fileId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Collector {
|
||||||
|
name: string;
|
||||||
|
max: number;
|
||||||
|
pipe?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CopyInFile = LocalFile | MemoryFile | PreparedFile;
|
||||||
|
export type CmdFile = LocalFile | MemoryFile | PreparedFile | Collector | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cmd defines a single command to be executed by sandbox server
|
||||||
|
*/
|
||||||
|
export interface Cmd {
|
||||||
|
args: string[];
|
||||||
|
env?: string[];
|
||||||
|
/** files defines open file descriptor for the command */
|
||||||
|
files?: CmdFile[];
|
||||||
|
tty?: boolean;
|
||||||
|
|
||||||
|
/** cpuLimit and clockLimit defines time limitations in ns */
|
||||||
|
cpuLimit?: number;
|
||||||
|
clockLimit?: number;
|
||||||
|
/** memoryLimit and stackLimit defines memory limitation in bytes */
|
||||||
|
memoryLimit?: number;
|
||||||
|
stackLimit?: number;
|
||||||
|
procLimit?: number;
|
||||||
|
/** cpuRateLimit defines cpu share limits in 1/1000 cpus if enabled in sandbox server */
|
||||||
|
cpuRateLimit?: number;
|
||||||
|
/** cpuSetLimit defines cpu set limit if enabled in sandbox server */
|
||||||
|
cpuSetLimit?: string;
|
||||||
|
/** strictMemoryLimit set rlimit_data limit for memoryLimit */
|
||||||
|
strictMemoryLimit?: boolean;
|
||||||
|
|
||||||
|
/** files to be copied into sandbox before execution */
|
||||||
|
copyIn?: Record<string, CopyInFile>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* files to be copied out from sandbox after execution.
|
||||||
|
* append '?' to make the file optional and do not cause FileError when missing
|
||||||
|
*/
|
||||||
|
copyOut?: string[];
|
||||||
|
/** similar to copyOut but fileId returned instead */
|
||||||
|
copyOutCached?: string[];
|
||||||
|
/** copyOut limit in byte */
|
||||||
|
copyOutMax?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SandboxRequest defines a single request to sandbox server
|
||||||
|
*/
|
||||||
|
export interface SandboxRequest {
|
||||||
|
requestId?: string;
|
||||||
|
cmd: Cmd[];
|
||||||
|
pipeMapping?: PipeMap[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SandboxStatus {
|
||||||
|
Accepted = 'Accepted',
|
||||||
|
MemoryLimitExceeded = 'Memory Limit Exceeded',
|
||||||
|
TimeLimitExceeded = 'Time Limit Exceeded',
|
||||||
|
OutputLimitExceeded = 'Output Limit Exceeded',
|
||||||
|
FileError = 'File Error',
|
||||||
|
NonzeroExitStatus = 'Nonzero Exit Status',
|
||||||
|
Signalled = 'Signalled',
|
||||||
|
InternalError = 'Internal Error',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PipeIndex {
|
||||||
|
index: number;
|
||||||
|
fd: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PipeMap {
|
||||||
|
in: PipeIndex;
|
||||||
|
out: PipeIndex;
|
||||||
|
/** enable pipe proxy */
|
||||||
|
proxy?: boolean;
|
||||||
|
/** if proxy enabled, save transmitted content */
|
||||||
|
name?: string;
|
||||||
|
max?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FileErrorType {
|
||||||
|
CopyInOpenFile = 'CopyInOpenFile',
|
||||||
|
CopyInCreateFile = 'CopyInCreateFile',
|
||||||
|
CopyInCopyContent = 'CopyInCopyContent',
|
||||||
|
CopyOutOpen = 'CopyOutOpen',
|
||||||
|
CopyOutNotRegularFile = 'CopyOutNotRegularFile',
|
||||||
|
CopyOutSizeExceeded = 'CopyOutSizeExceeded',
|
||||||
|
CopyOutCreateFile = 'CopyOutCreateFile',
|
||||||
|
CopyOutCopyContent = 'CopyOutCopyContent',
|
||||||
|
CollectSizeExceeded = 'CollectSizeExceeded',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileError {
|
||||||
|
name: string;
|
||||||
|
type: FileErrorType;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SandboxResult defines result from sandbox server
|
||||||
|
*/
|
||||||
|
export interface SandboxResult {
|
||||||
|
status: SandboxStatus;
|
||||||
|
/** contains error message if status is not Accept */
|
||||||
|
error?: string;
|
||||||
|
/** signal number if status is Signalled, otherwise exit status */
|
||||||
|
exitStatus: number;
|
||||||
|
/** cpu time in ns */
|
||||||
|
time: number;
|
||||||
|
/** peak memory in byte */
|
||||||
|
memory: number;
|
||||||
|
/** wall clock time in ns */
|
||||||
|
runTime: number;
|
||||||
|
/** copyOut file name to content (UTF-8 encoded and invalid character replaced) */
|
||||||
|
files?: Record<string, string>;
|
||||||
|
/** copyOutCached file name to fileId */
|
||||||
|
fileIds?: Record<string, string>;
|
||||||
|
/** contains detailed error if status is FileError */
|
||||||
|
fileError?: FileError[];
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import fs from 'fs-extra';
|
|||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
import { get as _get } from '@hydrooj/utils/lib/sysinfo';
|
import { get as _get } from '@hydrooj/utils/lib/sysinfo';
|
||||||
import { getConfig } from './config';
|
import { getConfig } from './config';
|
||||||
|
import { Context } from './judge/interface';
|
||||||
import { judge } from './judge/run';
|
import { judge } from './judge/run';
|
||||||
import * as tmpfs from './tmpfs';
|
import * as tmpfs from './tmpfs';
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ export { update } from '@hydrooj/utils/lib/sysinfo';
|
|||||||
async function stackSize() {
|
async function stackSize() {
|
||||||
let output = '';
|
let output = '';
|
||||||
try {
|
try {
|
||||||
const context: any = {
|
const context: Context = {
|
||||||
lang: 'cc',
|
lang: 'cc',
|
||||||
code: `#include <iostream>
|
code: `#include <iostream>
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@@ -33,8 +34,19 @@ int main(){
|
|||||||
if (data.case) output = data.case.message;
|
if (data.case) output = data.case.message;
|
||||||
},
|
},
|
||||||
end: () => { },
|
end: () => { },
|
||||||
|
getLang: () => ({
|
||||||
|
compile: '/usr/bin/g++ -Wall -std=c++14 -o foo.cc foo.cc -lm',
|
||||||
|
execute: 'foo',
|
||||||
|
code_file: 'foo.cc',
|
||||||
|
highlight: 'cpp astyle-c',
|
||||||
|
monaco: 'cpp',
|
||||||
|
display: 'C++',
|
||||||
|
time_limit_rate: 1,
|
||||||
|
domain: [],
|
||||||
|
key: '',
|
||||||
|
}),
|
||||||
|
tmpdir: path.resolve(getConfig('tmp_dir'), 'sysinfo'),
|
||||||
};
|
};
|
||||||
context.tmpdir = path.resolve(getConfig('tmp_dir'), 'sysinfo');
|
|
||||||
fs.ensureDirSync(context.tmpdir);
|
fs.ensureDirSync(context.tmpdir);
|
||||||
tmpfs.mount(context.tmpdir, '32m');
|
tmpfs.mount(context.tmpdir, '32m');
|
||||||
await judge(context).catch((e) => console.error(e));
|
await judge(context).catch((e) => console.error(e));
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ import log from './log';
|
|||||||
const linux = os.platform() === 'linux';
|
const linux = os.platform() === 'linux';
|
||||||
if (!linux) log.warn('Not running on linux. tmpfs disabled.');
|
if (!linux) log.warn('Not running on linux. tmpfs disabled.');
|
||||||
|
|
||||||
|
const userInfo = os.userInfo();
|
||||||
|
const uid = userInfo.uid;
|
||||||
|
if (uid !== 0) log.warn('Not running by root. tmpfs disabled.');
|
||||||
|
|
||||||
export function mount(path: string, size = '32m') {
|
export function mount(path: string, size = '32m') {
|
||||||
fs.ensureDirSync(path);
|
fs.ensureDirSync(path);
|
||||||
if (linux) child.execSync(`mount tmpfs ${path} -t tmpfs -o size=${size}`);
|
if (linux && uid === 0) child.execSync(`mount tmpfs ${path} -t tmpfs -o size=${size}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function umount(path: string) {
|
export function umount(path: string) {
|
||||||
if (linux) child.execSync(`umount ${path}`);
|
if (linux && uid === 0) child.execSync(`umount ${path}`);
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ export function parseFilename(filePath: string) {
|
|||||||
return t[t.length - 1];
|
return t[t.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const encrypt = (algorithm, content) => {
|
const encrypt = (algorithm: string, content: crypto.BinaryLike) => {
|
||||||
const hash = crypto.createHash(algorithm);
|
const hash = crypto.createHash(algorithm);
|
||||||
hash.update(content);
|
hash.update(content);
|
||||||
return hash.digest('hex');
|
return hash.digest('hex');
|
||||||
@@ -56,7 +56,7 @@ export class Queue<T> extends EventEmitter {
|
|||||||
export namespace Lock {
|
export namespace Lock {
|
||||||
const data = {};
|
const data = {};
|
||||||
|
|
||||||
export async function aquire(key: string) {
|
export async function acquire(key: string) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
while (data[key]) await sleep(100);
|
while (data[key]) await sleep(100);
|
||||||
data[key] = true;
|
data[key] = true;
|
||||||
|
|||||||
Reference in new issue
Block a user