Browse Source

Finish

master
t123yh 7 years ago
parent
commit
00a1d00582
  1. 375
      src/daemon-frontend-syzoj/socketio.ts

375
src/daemon-frontend-syzoj/socketio.ts

@ -8,11 +8,6 @@ import { globalConfig as Cfg } from './config';
import { convertResult } from '../judgeResult'; import { convertResult } from '../judgeResult';
import { JudgeResult, TaskStatus, CompilationResult, OverallResult } from '../interfaces'; import { JudgeResult, TaskStatus, CompilationResult, OverallResult } from '../interfaces';
interface JudgeData {
running: boolean;
current?: OverallResult;
}
interface RoughResult { interface RoughResult {
result: string; result: string;
score: number; score: number;
@ -28,19 +23,61 @@ let roughProgressNamespace: SocketIO.Namespace;
// can only see whether his / her submission is successfully compiled. // can only see whether his / her submission is successfully compiled.
let compileProgressNamespace: SocketIO.Namespace; let compileProgressNamespace: SocketIO.Namespace;
const currentJudgeList: JudgeData[] = []; const currentJudgeList: OverallResult[] = [];
const finishedJudgeList: RoughResult[] = []; const finishedJudgeList: RoughResult[] = [];
const compiledList = []; const compiledList = [];
// The detail progress is pushed to client in the delta form.
// However, the messages may arrive in an unorder form.
// In that case, the client will re-connect the server.
const clientDetailProgressList: { [id: string]: { version: number, content: OverallResult } } = {};
const clientDisplayConfigList: { [id: string]: DisplayConfig } = {};
interface DisplayConfig { interface DisplayConfig {
pushType: string; showScore: boolean;
hideScore: boolean; showUsage: boolean;
hideUsage: boolean; showCode: boolean;
hideCode: boolean; showResult: boolean;
hideResult: boolean; showDetailResult: boolean;
hideTestcaseDetails?: boolean; showTestdata: boolean;
inContest: boolean;
// hideTestcaseDetails?: boolean;
}; };
function processOverallResult(source: OverallResult, config: DisplayConfig): OverallResult {
if (source == null)
return null;
if (source.error != null) {
return {
error: source.error,
systemMessage: source.systemMessage
};
}
return {
compile: source.compile,
judge: config.showDetailResult ? (source.judge && {
subtasks: source.judge.subtasks && source.judge.subtasks.map(st => ({
score: st.score,
cases: st.cases.map(cs => ({
status: cs.status,
result: cs.result && {
type: cs.result.type,
time: config.showUsage ? cs.result.time : undefined,
memory: config.showUsage ? cs.result.memory : undefined,
scoringRate: cs.result.scoringRate,
systemMessage: cs.result.systemMessage,
input: config.showTestdata ? cs.result.input : undefined,
output: config.showTestdata ? cs.result.output : undefined,
userOutput: config.showTestdata ? cs.result.userOutput : undefined,
userError: config.showTestdata ? cs.result.userError : undefined,
spjMessage: config.showTestdata ? cs.result.spjMessage : undefined,
}
}))
}))
}) : null
}
}
function getCompileStatus(status: string): string { function getCompileStatus(status: string): string {
if (["System Error", "Compile Error", "No Testdata"].includes(status)) { if (["System Error", "Compile Error", "No Testdata"].includes(status)) {
return status; return status;
@ -50,157 +87,168 @@ function getCompileStatus(status: string): string {
} }
function processRoughResult(source: RoughResult, config: DisplayConfig): RoughResult { function processRoughResult(source: RoughResult, config: DisplayConfig): RoughResult {
const result = config.hideResult ? const result = config.showResult ?
getCompileStatus(source.result) : source.result :
source.result; getCompileStatus(source.result);
return { return {
result: result, result: result,
time: config.hideUsage ? null : source.time, time: config.showUsage ? source.time : null,
memory: config.hideUsage ? null : source.memory, memory: config.showUsage ? source.memory : null,
score: config.hideUsage ? null : source.score score: config.showUsage ? source.score : null
}; };
} }
const clientList: { [id: string]: DisplayConfig } = {}; function forAllClients(ns: SocketIO.Namespace, taskId: number, exec: (socketId: string) => void): void {
ns.in(taskId.toString()).clients((err, clients) => {
if (!err) {
clients.forEach(client => {
exec(client);
});
} else {
winston.warn(`Error while listing socketio clients in ${taskId}`, err);
}
});
}
export function initializeSocketIO(s: http.Server) { export function initializeSocketIO(s: http.Server) {
ioInstance = socketio(s); ioInstance = socketio(s);
detailProgressNamespace = ioInstance.of('/detail');
roughProgressNamespace = ioInstance.of('/rough');
compileProgressNamespace = ioInstance.of('/compile');
// TODO: deduplicate the following code. const initializeNamespace = (name, exec: (token: any, socket: SocketIO.Socket) => Promise<any>) => {
detailProgressNamespace.on('connection', (socket) => { const newNamespace = ioInstance.of('/' + name);
socket.on('join', (reqJwt, cb) => { newNamespace.on('connection', (socket) => {
let req; socket.on('disconnect', () => {
try { winston.info(`Client ${socket.id} disconnected.`);
req = jwt.verify(reqJwt, Cfg.token); delete clientDisplayConfigList[socket.id];
if (req.type !== 'detail') { if (clientDetailProgressList[socket.id]) {
throw new Error("Request type in token mismatch."); delete clientDetailProgressList[socket.id];
} }
} catch (err) { });
winston.info('The client has an incorrect token.'); socket.on('join', (reqJwt, cb) => {
cb({ winston.info(`Client ${socket.id} connected.`);
ok: false, let req;
message: err.toString() try {
}); req = jwt.verify(reqJwt, Cfg.token);
return; if (req.type !== name) {
} throw new Error("Request type in token mismatch.");
const taskId = req.taskId; }
winston.verbose(`A client trying to get detailed progress for ${taskId}.`); } catch (err) {
socket.join(taskId.toString()); winston.info('The client has an incorrect token.');
if (finishedJudgeList[taskId]) { cb({
winston.debug(`Judge task #${taskId} has been finished, ${JSON.stringify(currentJudgeList[taskId])}`); ok: false,
cb({ message: err.toString()
ok: true, });
finished: true, return;
result: currentJudgeList[taskId],
roughResult: finishedJudgeList[taskId]
});
} else {
winston.debug(`Judge task #${taskId} has not been finished`);
cb({
ok: true,
finished: false,
current: currentJudgeList[taskId] || { running: false }
});
}
});
});
roughProgressNamespace.on('connection', (socket) => {
socket.on('disconnect', () => {
delete clientList[socket.id];
})
socket.on('join', (reqJwt, cb) => {
let req;
try {
req = jwt.verify(reqJwt, Cfg.token);
if (req.type !== 'rough') {
throw new Error("Permission denied");
} }
clientList[socket.id] = req.displayConfig; const taskId = req.taskId;
} catch (err) { winston.verbose(`A client trying to join ${name} namespace for ${taskId}.`);
cb({ socket.join(taskId.toString());
ok: false, exec(req, socket).then(x => cb(x), err => cb({ ok: false, message: err.toString() }));
message: err.toString() });
}); });
return; return newNamespace;
} };
const taskId = req.taskId;
socket.join(taskId.toString()); detailProgressNamespace = initializeNamespace('detail', async (req, socket) => {
const taskId = req.taskId;
if (finishedJudgeList[taskId]) {
winston.debug(`Judge task #${taskId} has been finished, ${JSON.stringify(currentJudgeList[taskId])}`);
return {
ok: true,
running: false,
finished: true,
result: processOverallResult(currentJudgeList[taskId], clientDisplayConfigList[socket.id]),
roughResult: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[socket.id])
};
} else {
winston.debug(`Judge task #${taskId} has not been finished`);
// If running
if (currentJudgeList[taskId]) { if (currentJudgeList[taskId]) {
cb({ clientDetailProgressList[socket.id] = {
version: 0,
content: processOverallResult(currentJudgeList[taskId], clientDisplayConfigList[socket.id])
};
return {
ok: true, ok: true,
finished: false,
running: true, running: true,
finished: false current: clientDetailProgressList[socket.id]
}); };
} else if (finishedJudgeList[taskId]) {
// This is not likely to happen. If some task is processed,
// The client should not process it.
const result = finishedJudgeList[taskId];
cb({
ok: true,
running: false,
finished: true,
result: processRoughResult(result, clientList[socket.id])
});
} else { } else {
cb({ // If not running yet, the creation of clientDetailProgressList
// will be done in the starting procedure (createTask function).
return {
ok: true, ok: true,
running: false, finished: false,
finished: false running: false
}) };
} }
}); }
}); });
compileProgressNamespace.on('connection', (socket) => { roughProgressNamespace = initializeNamespace('rough', async (req, socket) => {
socket.on('join', (reqJwt, cb) => { const taskId = req.taskId;
let req; if (currentJudgeList[taskId]) {
try { return {
req = jwt.verify(reqJwt, Cfg.token); ok: true,
if (req.type !== 'compile') { running: true,
throw new Error("Request type in token mismatch."); finished: false
} };
} catch (err) { } else if (finishedJudgeList[taskId]) {
cb({ return {
ok: false, ok: true,
message: err.toString() running: false,
}); finished: true,
return; result: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[socket.id])
} };
const taskId = req.taskId; } else {
socket.join(taskId.toString()); return {
if (compiledList[taskId]) { ok: true,
cb({ running: false,
ok: true, finished: false
running: false, };
finished: true, }
result: compiledList[taskId] });
});
} else if (currentJudgeList[taskId]) { compileProgressNamespace = initializeNamespace('compile', async (req, socket) => {
cb({ const taskId = req.taskId;
ok: true, if (compiledList[taskId]) {
running: true, return {
finished: false ok: true,
}); running: false,
} else { finished: true,
cb({ result: compiledList[taskId]
ok: true, };
running: false, } else if (currentJudgeList[taskId]) {
finished: false return {
}); ok: true,
} running: true,
}); finished: false
};
} else {
return {
ok: true,
running: false,
finished: false
};
}
}); });
} }
export function createTask(taskId: number) { export function createTask(taskId: number) {
winston.debug(`Judge task #${taskId} has started`); winston.debug(`Judge task #${taskId} has started`);
detailProgressNamespace.to(taskId.toString()).emit("start", { taskId: taskId });
currentJudgeList[taskId] = {};
forAllClients(detailProgressNamespace, taskId, (clientId) => {
clientDetailProgressList[clientId] = {
version: 0,
content: currentJudgeList[taskId]
};
});
roughProgressNamespace.to(taskId.toString()).emit("start", { taskId: taskId }); roughProgressNamespace.to(taskId.toString()).emit("start", { taskId: taskId });
detailProgressNamespace.to(taskId.toString()).emit("start", { taskId: taskId });
compileProgressNamespace.to(taskId.toString()).emit("start", { taskId: taskId }); compileProgressNamespace.to(taskId.toString()).emit("start", { taskId: taskId });
currentJudgeList[taskId] = { running: true, current: null };
} }
export function updateCompileStatus(taskId: number, result: CompilationResult) { export function updateCompileStatus(taskId: number, result: CompilationResult) {
@ -214,19 +262,28 @@ export function updateCompileStatus(taskId: number, result: CompilationResult) {
} }
export function updateProgress(taskId: number, data: OverallResult) { export function updateProgress(taskId: number, data: OverallResult) {
winston.debug(`Updating progress for #${taskId}, data: ${JSON.stringify(data)}`); winston.verbose(`Updating progress for #${taskId}, data: ${JSON.stringify(data)}`);
const original = currentJudgeList[taskId].current;
const delta = diff.diff(original, data); currentJudgeList[taskId] = data;
detailProgressNamespace.to(taskId.toString()).emit('update', { forAllClients(detailProgressNamespace, taskId, (client) => {
taskId: taskId, winston.debug(`Pushing progress update to ${client}`)
delta: delta if (clientDetailProgressList[client] && clientDisplayConfigList[client]) { // avoid race condition
const original = clientDetailProgressList[client].content;
const updated = processOverallResult(currentJudgeList[taskId], clientDisplayConfigList[client]);
const version = clientDetailProgressList[taskId].version;
detailProgressNamespace.sockets[client].emit('update', {
taskId: taskId,
from: version,
to: version + 1,
delta: diff.diff(original, updated)
})
clientDetailProgressList[taskId].version++;
}
}); });
currentJudgeList[taskId].current = data;
} }
export function updateResult(taskId: number, data: OverallResult) { export function updateResult(taskId: number, data: OverallResult) {
currentJudgeList[taskId].running = false; currentJudgeList[taskId] = data;
currentJudgeList[taskId].current = data;
if (compiledList[taskId] == null) { if (compiledList[taskId] == null) {
if (data.error != null) { if (data.error != null) {
@ -246,20 +303,26 @@ export function updateResult(taskId: number, data: OverallResult) {
score: finalResult.score score: finalResult.score
}; };
finishedJudgeList[taskId] = roughResult; finishedJudgeList[taskId] = roughResult;
roughProgressNamespace.to(taskId.toString()).clients((err, clients) => {
for (const client of clients) { forAllClients(roughProgressNamespace, taskId, (client) => {
winston.debug(`Pushing rough result to ${client}`) winston.debug(`Pushing rough result to ${client}`)
roughProgressNamespace.sockets[client].emit('finish', { roughProgressNamespace.sockets[client].emit('finish', {
taskId: taskId,
result: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[client])
});
});
forAllClients(detailProgressNamespace, taskId, (client) => {
if (clientDisplayConfigList[client]) { // avoid race condition
winston.debug(`Pushing detail result to ${client}`)
detailProgressNamespace.sockets[client].emit('finish', {
taskId: taskId, taskId: taskId,
result: processRoughResult(finishedJudgeList[taskId], clientList[client]) result: processOverallResult(currentJudgeList[taskId], clientDisplayConfigList[client]),
roughResult: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[client])
}); });
delete clientDetailProgressList[client];
} }
}); });
detailProgressNamespace.to(taskId.toString()).emit('finish', {
taskId: taskId,
roughResult: finishedJudgeList[taskId],
result: data
});
} }
export function cleanupProgress(taskId: number) { export function cleanupProgress(taskId: number) {

Loading…
Cancel
Save