Created
January 24, 2023 11:17
-
-
Save IPRIT/0c23dc4cf51a3ad8de65b7c92e9ae322 to your computer and use it in GitHub Desktop.
Ejudge
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}`; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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('_')}`; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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