Skip to content

Instantly share code, notes, and snippets.

@IPRIT
Created January 24, 2023 11:17
Show Gist options
  • Save IPRIT/0c23dc4cf51a3ad8de65b7c92e9ae322 to your computer and use it in GitHub Desktop.
Save IPRIT/0c23dc4cf51a3ad8de65b7c92e9ae322 to your computer and use it in GitHub Desktop.
Ejudge
import request from 'request-promise';
import cheerio from 'cheerio';
import { config } from "../../../utils";
import { getClearedSolutionId } from "./getVerdict";
const ACM_SOLUTIONS_ENDPOINT = '/cgi-bin/new-judge';
export async function getCompilationError(systemAccount, contextRow) {
let { jar, sid } = systemAccount;
let { solutionId } = contextRow;
let clearedSolutionId = getClearedSolutionId(solutionId);
let endpoint = getEndpoint(ACM_SOLUTIONS_ENDPOINT);
let response = await request({
method: 'GET',
uri: endpoint,
qs: {
SID: sid,
run_id: clearedSolutionId,
action: 37
},
simple: false,
resolveWithFullResponse: true,
followAllRedirects: true,
encoding: 'utf8',
jar
});
if (!response.body || ![ 200 ].includes(response.statusCode)) {
return 'Service Unavailable';
}
let $ = cheerio.load(response.body);
return $('div#container pre').text();
}
function getEndpoint(pathTo = '') {
let { host, protocol } = config.ejudge;
return `${protocol}://${host}${pathTo}`;
}
import Promise from 'bluebird';
import * as models from '../../../models';
import * as sockets from '../../socket';
import { getFreeAccount } from "./accountsPool";
import { sendSolution } from "./sendSolution";
import { getVerdict } from "./getVerdict";
import { getCompilationError } from "./getCompilationError";
import { ensureNumber } from "../../../utils";
import filter from "../../../utils/filter";
const maxAttemptsNumber = 3;
const nextAttemptAfterMs = 10 * 1000;
const serviceUnavailableVerdictId = 13;
const verdictCheckTimeoutMs = 100;
const maxAccountWaitingMs = 5 * 60 * 1000;
export async function handle(solution) {
let systemAccount = await getFreeAccount();
systemAccount.busy();
try {
let contextRow = await sendSolution(solution, systemAccount);
let verdict;
while (!verdict || !verdict.isTerminal) {
if (systemAccount.lastSentSolutionAtMs + maxAccountWaitingMs < Date.now()) {
throw new Error('Time limit has exceeded');
}
verdict = await getVerdict(solution, systemAccount, contextRow);
console.log(verdict);
let socketData = {
contestId: solution.contestId,
solution: filter(Object.assign(solution.get({ plain: true }), { verdict }), {
exclude: [ 'sourceCode' ]
})
};
sockets.emitVerdictUpdateEvent(socketData);
sockets.emitUserSolutionsEvent(solution.userId, 'verdict updated', socketData.solution);
sockets.emitAdminSolutionsEvent('verdict updated', socketData.solution);
await Promise.delay(verdictCheckTimeoutMs);
}
if (verdict.id === 3) {
let compilationError = await getCompilationError(systemAccount, contextRow);
Object.assign(verdict, { compilationError });
}
return saveVerdict(solution, systemAccount, verdict);
} catch (error) {
console.error(error);
await handleError(error, solution, systemAccount);
}
}
async function handleError(error, solution, systemAccount) {
Promise.delay(10 * 1000).then(_ => systemAccount.free());
solution.retriesNumber++;
if (solution.retriesNumber >= maxAttemptsNumber) {
let verdictId = serviceUnavailableVerdictId;
let verdict = await models.Verdict.findByPrimary(verdictId);
await solution.update({
retriesNumber: solution.retriesNumber,
verdictId: verdict.id,
verdictGotAtMs: Date.now(),
errorTrace: (error || '').toString()
});
let socketData = {
contestId: solution.contestId,
solution: filter(Object.assign(solution.get({ plain: true }), {
verdict: {
id: verdict.id,
isTerminal: true,
name: verdict.name,
testNumber: 0,
executionTime: 0,
memory: 0
}
}), {
exclude: [ 'sourceCode' ]
})
};
sockets.emitVerdictUpdateEvent(socketData);
sockets.emitUserSolutionsEvent(solution.userId, 'verdict updated', socketData.solution);
sockets.emitAdminSolutionsEvent('verdict updated', socketData.solution);
} else {
await solution.update({
retriesNumber: solution.retriesNumber,
nextAttemptWillBeAtMs: Date.now() + nextAttemptAfterMs * solution.retriesNumber
});
let socketData = {
contestId: solution.contestId,
solution: filter(Object.assign(solution.get({ plain: true }), {
_currentAttempt: solution.retriesNumber
}), {
exclude: [ 'sourceCode' ]
})
};
sockets.emitVerdictUpdateEvent(socketData);
sockets.emitUserSolutionsEvent(solution.userId, 'verdict updated', socketData.solution);
sockets.emitAdminSolutionsEvent('verdict updated', socketData.solution);
}
}
async function saveVerdict(solution, systemAccount, verdict) {
await solution.update({
verdictGotAtMs: Date.now(),
testNumber: verdict.id === 1 ? 0 : ensureNumber(verdict.testNumber),
executionTime: verdict.executionTime,
memory: verdict.memory,
verdictId: verdict.id,
compilationError: verdict.compilationError
});
systemAccount.free();
let socketData = {
contestId: solution.contestId,
solution: filter(Object.assign(solution.get({ plain: true }), { verdict }), {
exclude: [ 'sourceCode' ]
})
};
sockets.emitVerdictUpdateEvent(socketData);
sockets.emitUserSolutionsEvent(solution.userId, 'verdict updated', socketData.solution);
sockets.emitAdminSolutionsEvent('verdict updated', socketData.solution);
sockets.emitTableUpdateEvent({
contestId: solution.contestId
});
return solution;
}
import Promise from 'bluebird';
import cheerio from 'cheerio';
import request from 'request-promise';
import stringToStream from 'string-to-stream';
import * as models from '../../../models';
import { login, parseProblemIdentifier } from "./account";
import { getSymbolIndex } from "../../../utils";
import config from "../../../utils/config";
const ACM_SOLUTIONS_ENDPOINT = '/cgi-bin/new-judge';
export async function sendSolution(solution, systemAccount) {
return Promise.resolve().then(async () => {
await login(solution, systemAccount);
await Promise.delay(1000);
let endpoint = getEndpoint(ACM_SOLUTIONS_ENDPOINT);
let { sid, jar } = systemAccount;
let response = await request({
method: 'POST',
uri: endpoint,
formData: buildPostForm(solution, systemAccount),
simple: false,
resolveWithFullResponse: true,
followAllRedirects: true,
encoding: 'utf8',
jar
});
if (!response.body || ![ 200 ].includes(response.statusCode)) {
throw new Error('Service Unavailable');
}
await Promise.delay(500);
endpoint = getEndpoint(ACM_SOLUTIONS_ENDPOINT);
response = await request({
method: 'GET',
uri: endpoint,
qs: {
SID: sid
},
simple: false,
resolveWithFullResponse: true,
followAllRedirects: true,
encoding: 'utf8',
jar
});
if (!response.body || ![ 200 ].includes(response.statusCode)) {
throw new Error('Service Unavailable');
}
let $ = cheerio.load(response.body);
let [ contestId, problemNumber ] = parseProblemIdentifier(solution.Problem.foreignProblemIdentifier);
let rows = [];
$('table.b1').find('tr').slice(1).each((index, row) => {
let $row = $(row);
let sentIdDirty = $row.find('td').eq(0).text();
let probablySolutionId;
try {
probablySolutionId = Number(sentIdDirty.match(/(\d+)/i)[1]);
} catch (error) {
return;
}
let username = $row.find('td').eq(2).text().trim();
let symbolIndex = $row.find('td').eq(3).text().trim().toLowerCase();
rows.push({
solutionId: getEjudgeSolutionId(contestId, probablySolutionId),
username,
symbolIndex
});
});
let contextRow;
for (let row of rows) {
if (row.symbolIndex === getSymbolIndex(problemNumber - 1)
|| row.username.toLowerCase() === systemAccount.instance.systemLogin.trim().toLowerCase()) {
let existSolution = await models.Solution.findOne({
where: {
internalSolutionIdentifier: row.solutionId
}
});
if (existSolution) {
throw new Error('Solution did not send');
}
contextRow = row;
break;
}
}
if (!contextRow) {
throw new Error('Solution did not send');
}
await solution.update({
internalSolutionIdentifier: contextRow.solutionId
});
return contextRow;
});
}
function getEndpoint(pathTo = '') {
let { host, protocol } = config.ejudge;
return `${protocol}://${host}${pathTo}`;
}
function buildPostForm(solution, systemAccount) {
let { sid } = systemAccount;
let [ contestId, problemNumber ] = parseProblemIdentifier(solution.Problem.foreignProblemIdentifier);
return {
'SID': sid,
'problem': problemNumber,
'lang_id': solution.Language.foreignLanguageId,
'file': {
value: stringToStream(solution.sourceCode),
options: {
filename: 'source.txt',
contentType: 'text/plain',
knownLength: solution.sourceCode.length
}
},
'action_40': 'Send!'
};
}
export function getEjudgeSolutionId(...args) {
return `ejudge${args.join('_')}`;
}
import Promise from 'bluebird';
import request from 'request-promise';
import cheerio from 'cheerio';
import * as models from '../../../models';
import { getEjudgeSolutionId } from "./sendSolution";
import { parseProblemIdentifier } from "./account";
import { ensureNumber } from "../../../utils";
import config from "../../../utils/config";
const terminalStatesMapping = {
'OK': 1,
'Wrong answer': 2,
'Compilation error': 3,
'Run-time error': 4,
'Output limit exceeded': 5,
'Presentation error': 5,
'Time-limit exceeded': 6,
'Memory limit exceeded': 7,
'Idleness limit exceeded': 8,
'Restricted function': 9,
'Check failed': 4,
'Partial solution': 2,
'Partial': 2,
'Ignored': 11,
'Disqualified': 12,
'Security violation': 9,
'Coding style violation': 9,
'Wall time-limit exceeded': 6,
'Pending review': 11,
'Rejected': 11,
'Full rejudge': 11,
'Rejudge': 11,
'No change': 11
};
const ACM_SOLUTIONS_ENDPOINT = '/cgi-bin/new-judge';
export async function getVerdict(solution, systemAccount, receivedRow) {
return Promise.resolve().then(async () => {
let contextRow = await getContextRow(solution, systemAccount, receivedRow);
let { testNumber, executionTime, memory, verdictName } = contextRow;
let verdictId = getVerdictId(verdictName);
let verdictInstance = await models.Verdict.findByPrimary(verdictId);
return {
id: verdictInstance && verdictInstance.id,
isTerminal: isTerminal(verdictName),
name: verdictInstance ? verdictInstance.name : verdictName,
testNumber: verdictId === 1 ? 0 : testNumber,
executionTime,
memory
}
});
}
function isTerminal(verdictName = '') {
verdictName = verdictName.toLowerCase();
return Object.keys(terminalStatesMapping).some(state => {
return verdictName.indexOf(state.toLowerCase()) !== -1;
});
}
function getVerdictId(verdictName) {
verdictName = verdictName.toLowerCase();
let verdicts = Object.keys(terminalStatesMapping).filter(state => {
return verdictName.indexOf(state.toLowerCase()) !== -1;
});
if (!verdicts.length) {
return null;
}
return terminalStatesMapping[ verdicts[0] ];
}
async function getContextRow(solution, systemAccount, receivedRow) {
let [ contestId, problemNumber ] = parseProblemIdentifier(solution.Problem.foreignProblemIdentifier);
let { jar, sid } = systemAccount;
let attemptsNumber = 0, maxAttemptsNumber = 3;
while (attemptsNumber < maxAttemptsNumber) {
await Promise.delay(1000 + attemptsNumber * 500);
attemptsNumber++;
let endpoint = getEndpoint(ACM_SOLUTIONS_ENDPOINT);
let response = await request({
method: 'GET',
uri: endpoint,
qs: {
SID: sid
},
simple: false,
resolveWithFullResponse: true,
followAllRedirects: true,
encoding: 'utf8',
jar
});
if (!response.body || ![ 200 ].includes(response.statusCode)) {
throw new Error('Service Unavailable');
}
let $ = cheerio.load(response.body);
let rows = [];
$('table.b1').find('tr').slice(1).each((index, row) => {
let $row = $(row);
let sentIdDirty = $row.find('td').eq(0).text();
try {
let probablySolutionId = ensureNumber(sentIdDirty.match(/(\d+)/i)[1]);
let username = $row.find('td').eq(2).text().trim();
let symbolIndex = $row.find('td').eq(3).text().trim().toLowerCase();
let verdictName = $row.find('td').eq(5).find('a').eq(0).text().trim();
let testNumber = ensureNumber($row.find('td').eq(6).text());
rows.push({
solutionId: getEjudgeSolutionId(contestId, probablySolutionId),
username,
symbolIndex,
verdictName,
testNumber,
executionTime: 0,
memory: 0
});
} catch (error) {}
});
for (let row of rows) {
if (row.solutionId === receivedRow.solutionId) {
let solutionInfo = await getSolutionInfo(systemAccount, row);
console.log(solutionInfo);
if (solutionInfo) {
Object.assign(row, solutionInfo);
}
return row;
}
}
}
return null;
}
async function getSolutionInfo(systemAccount, receivedRow) {
let { jar, sid } = systemAccount;
let { solutionId } = receivedRow;
let clearedSolutionId = getClearedSolutionId(solutionId);
let endpoint = getEndpoint(ACM_SOLUTIONS_ENDPOINT);
let response = await request({
method: 'GET',
uri: endpoint,
qs: {
SID: sid,
run_id: clearedSolutionId,
action: 37
},
simple: false,
resolveWithFullResponse: true,
followAllRedirects: true,
encoding: 'utf8',
jar
});
if (!response.body || ![ 200 ].includes(response.statusCode)) {
return null;
}
let $ = cheerio.load(response.body);
let table = $('table.b1');
if (!table.length) {
return null;
}
let lastTableRow = table.find('tr').slice(1).last();
let tds = lastTableRow.find('td');
return {
executionTime: ensureNumber( tds.eq(3).text() ),
memory: (ensureNumber( tds.eq(4).text() ) || 0) / 1024 // convert from bytes to Kbytes
};
}
export function getClearedSolutionId(solutionId = '') {
return ensureNumber( solutionId.match(/(\d+)$/i)[1] );
}
function getEndpoint(pathTo = '') {
let { host, protocol } = config.ejudge;
return `${protocol}://${host}${pathTo}`;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment