Browse Source

Finish

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

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

@ -8,11 +8,6 @@ import { globalConfig as Cfg } from './config';
import { convertResult } from '../judgeResult';
import { JudgeResult, TaskStatus, CompilationResult, OverallResult } from '../interfaces';
interface JudgeData {
running: boolean;
current?: OverallResult;
}
interface RoughResult {
result: string;
score: number;
@ -28,19 +23,61 @@ let roughProgressNamespace: SocketIO.Namespace;
// can only see whether his / her submission is successfully compiled.
let compileProgressNamespace: SocketIO.Namespace;
const currentJudgeList: JudgeData[] = [];
const currentJudgeList: OverallResult[] = [];
const finishedJudgeList: RoughResult[] = [];
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 {
pushType: string;
hideScore: boolean;
hideUsage: boolean;
hideCode: boolean;
hideResult: boolean;
hideTestcaseDetails?: boolean;
showScore: boolean;
showUsage: boolean;
showCode: boolean;
showResult: boolean;
showDetailResult: 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 {
if (["System Error", "Compile Error", "No Testdata"].includes(status)) {
return status;
@ -50,32 +87,49 @@ function getCompileStatus(status: string): string {
}
function processRoughResult(source: RoughResult, config: DisplayConfig): RoughResult {
const result = config.hideResult ?
getCompileStatus(source.result) :
source.result;
const result = config.showResult ?
source.result :
getCompileStatus(source.result);
return {
result: result,
time: config.hideUsage ? null : source.time,
memory: config.hideUsage ? null : source.memory,
score: config.hideUsage ? null : source.score
time: config.showUsage ? source.time : null,
memory: config.showUsage ? source.memory : null,
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) {
ioInstance = socketio(s);
detailProgressNamespace = ioInstance.of('/detail');
roughProgressNamespace = ioInstance.of('/rough');
compileProgressNamespace = ioInstance.of('/compile');
// TODO: deduplicate the following code.
detailProgressNamespace.on('connection', (socket) => {
const initializeNamespace = (name, exec: (token: any, socket: SocketIO.Socket) => Promise<any>) => {
const newNamespace = ioInstance.of('/' + name);
newNamespace.on('connection', (socket) => {
socket.on('disconnect', () => {
winston.info(`Client ${socket.id} disconnected.`);
delete clientDisplayConfigList[socket.id];
if (clientDetailProgressList[socket.id]) {
delete clientDetailProgressList[socket.id];
}
});
socket.on('join', (reqJwt, cb) => {
winston.info(`Client ${socket.id} connected.`);
let req;
try {
req = jwt.verify(reqJwt, Cfg.token);
if (req.type !== 'detail') {
if (req.type !== name) {
throw new Error("Request type in token mismatch.");
}
} catch (err) {
@ -87,120 +141,114 @@ export function initializeSocketIO(s: http.Server) {
return;
}
const taskId = req.taskId;
winston.verbose(`A client trying to get detailed progress for ${taskId}.`);
winston.verbose(`A client trying to join ${name} namespace for ${taskId}.`);
socket.join(taskId.toString());
exec(req, socket).then(x => cb(x), err => cb({ ok: false, message: err.toString() }));
});
});
return newNamespace;
};
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])}`);
cb({
return {
ok: true,
running: false,
finished: true,
result: currentJudgeList[taskId],
roughResult: finishedJudgeList[taskId]
});
result: processOverallResult(currentJudgeList[taskId], clientDisplayConfigList[socket.id]),
roughResult: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[socket.id])
};
} else {
winston.debug(`Judge task #${taskId} has not been finished`);
cb({
// If running
if (currentJudgeList[taskId]) {
clientDetailProgressList[socket.id] = {
version: 0,
content: processOverallResult(currentJudgeList[taskId], clientDisplayConfigList[socket.id])
};
return {
ok: true,
finished: false,
current: currentJudgeList[taskId] || { running: false }
});
running: true,
current: clientDetailProgressList[socket.id]
};
} else {
// If not running yet, the creation of clientDetailProgressList
// will be done in the starting procedure (createTask function).
return {
ok: true,
finished: false,
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;
} catch (err) {
cb({
ok: false,
message: err.toString()
});
return;
}
roughProgressNamespace = initializeNamespace('rough', async (req, socket) => {
const taskId = req.taskId;
socket.join(taskId.toString());
if (currentJudgeList[taskId]) {
cb({
return {
ok: true,
running: true,
finished: false
});
};
} 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({
return {
ok: true,
running: false,
finished: true,
result: processRoughResult(result, clientList[socket.id])
});
result: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[socket.id])
};
} else {
cb({
return {
ok: true,
running: false,
finished: false
})
};
}
});
});
compileProgressNamespace.on('connection', (socket) => {
socket.on('join', (reqJwt, cb) => {
let req;
try {
req = jwt.verify(reqJwt, Cfg.token);
if (req.type !== 'compile') {
throw new Error("Request type in token mismatch.");
}
} catch (err) {
cb({
ok: false,
message: err.toString()
});
return;
}
compileProgressNamespace = initializeNamespace('compile', async (req, socket) => {
const taskId = req.taskId;
socket.join(taskId.toString());
if (compiledList[taskId]) {
cb({
return {
ok: true,
running: false,
finished: true,
result: compiledList[taskId]
});
};
} else if (currentJudgeList[taskId]) {
cb({
return {
ok: true,
running: true,
finished: false
});
};
} else {
cb({
return {
ok: true,
running: false,
finished: false
});
};
}
});
});
}
export function createTask(taskId: number) {
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 });
detailProgressNamespace.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) {
@ -214,19 +262,28 @@ export function updateCompileStatus(taskId: number, result: CompilationResult) {
}
export function updateProgress(taskId: number, data: OverallResult) {
winston.debug(`Updating progress for #${taskId}, data: ${JSON.stringify(data)}`);
const original = currentJudgeList[taskId].current;
const delta = diff.diff(original, data);
detailProgressNamespace.to(taskId.toString()).emit('update', {
winston.verbose(`Updating progress for #${taskId}, data: ${JSON.stringify(data)}`);
currentJudgeList[taskId] = data;
forAllClients(detailProgressNamespace, taskId, (client) => {
winston.debug(`Pushing progress update to ${client}`)
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,
delta: delta
from: version,
to: version + 1,
delta: diff.diff(original, updated)
})
clientDetailProgressList[taskId].version++;
}
});
currentJudgeList[taskId].current = data;
}
export function updateResult(taskId: number, data: OverallResult) {
currentJudgeList[taskId].running = false;
currentJudgeList[taskId].current = data;
currentJudgeList[taskId] = data;
if (compiledList[taskId] == null) {
if (data.error != null) {
@ -246,19 +303,25 @@ export function updateResult(taskId: number, data: OverallResult) {
score: finalResult.score
};
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}`)
roughProgressNamespace.sockets[client].emit('finish', {
taskId: taskId,
result: processRoughResult(finishedJudgeList[taskId], clientList[client])
result: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[client])
});
}
});
detailProgressNamespace.to(taskId.toString()).emit('finish', {
forAllClients(detailProgressNamespace, taskId, (client) => {
if (clientDisplayConfigList[client]) { // avoid race condition
winston.debug(`Pushing detail result to ${client}`)
detailProgressNamespace.sockets[client].emit('finish', {
taskId: taskId,
roughResult: finishedJudgeList[taskId],
result: data
result: processOverallResult(currentJudgeList[taskId], clientDisplayConfigList[client]),
roughResult: processRoughResult(finishedJudgeList[taskId], clientDisplayConfigList[client])
});
delete clientDetailProgressList[client];
}
});
}

Loading…
Cancel
Save