mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-05-18 15:26:36 +00:00
refactor(judge): rewrite judge service to use database submissions
- Change return type from JudgeResult to Submission - Add database operations to persist submission records - Improve error handling and container cleanup - Update compile and run functions to work with submissions - Add proper status updates throughout the process
This commit is contained in:
parent
dc939085bb
commit
d64f95b4e7
@ -4,11 +4,11 @@ import fs from "fs";
|
|||||||
import tar from "tar-stream";
|
import tar from "tar-stream";
|
||||||
import Docker from "dockerode";
|
import Docker from "dockerode";
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Readable, Writable } from "stream";
|
import { Readable, Writable } from "stream";
|
||||||
import { ExitCode, EditorLanguage, JudgeResult } from "@/generated/client";
|
import { Status } from "@/generated/client";
|
||||||
|
import type { EditorLanguage, Submission } from "@/generated/client";
|
||||||
|
|
||||||
const isRemote = process.env.DOCKER_HOST_MODE === "remote";
|
const isRemote = process.env.DOCKER_HOST_MODE === "remote";
|
||||||
|
|
||||||
@ -25,11 +25,16 @@ const docker = isRemote
|
|||||||
: new Docker({ socketPath: "/var/run/docker.sock" });
|
: new Docker({ socketPath: "/var/run/docker.sock" });
|
||||||
|
|
||||||
// Prepare Docker image environment
|
// Prepare Docker image environment
|
||||||
async function prepareEnvironment(image: string, tag: string) {
|
async function prepareEnvironment(image: string, tag: string): Promise<boolean> {
|
||||||
const reference = `${image}:${tag}`;
|
try {
|
||||||
const filters = { reference: [reference] };
|
const reference = `${image}:${tag}`;
|
||||||
const images = await docker.listImages({ filters });
|
const filters = { reference: [reference] };
|
||||||
if (images.length === 0) await docker.pull(reference);
|
const images = await docker.listImages({ filters });
|
||||||
|
return images.length !== 0;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking Docker images:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Docker container with keep-alive
|
// Create Docker container with keep-alive
|
||||||
@ -64,15 +69,35 @@ function createTarStream(file: string, value: string) {
|
|||||||
|
|
||||||
export async function judge(
|
export async function judge(
|
||||||
language: EditorLanguage,
|
language: EditorLanguage,
|
||||||
value: string,
|
code: string,
|
||||||
problemId: string,
|
problemId: string,
|
||||||
): Promise<JudgeResult> {
|
): Promise<Submission> {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
if (!session) redirect("/sign-in");
|
if (!session?.user?.id) redirect("/sign-in");
|
||||||
|
|
||||||
|
const userId = session.user.id;
|
||||||
let container: Docker.Container | null = null;
|
let container: Docker.Container | null = null;
|
||||||
|
let submission: Submission | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const problem = await prisma.problem.findUnique({
|
||||||
|
where: { id: problemId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!problem) {
|
||||||
|
submission = await prisma.submission.create({
|
||||||
|
data: {
|
||||||
|
language,
|
||||||
|
code,
|
||||||
|
status: Status.SE,
|
||||||
|
userId,
|
||||||
|
problemId,
|
||||||
|
message: "Problem not found",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return submission;
|
||||||
|
}
|
||||||
|
|
||||||
const config = await prisma.editorLanguageConfig.findUnique({
|
const config = await prisma.editorLanguageConfig.findUnique({
|
||||||
where: { language },
|
where: { language },
|
||||||
include: {
|
include: {
|
||||||
@ -80,32 +105,18 @@ export async function judge(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!config || !config.dockerConfig) {
|
if (!config?.dockerConfig) {
|
||||||
return {
|
submission = await prisma.submission.create({
|
||||||
id: uuid(),
|
data: {
|
||||||
output: "Configuration Error: Missing editor or docker configuration",
|
language,
|
||||||
exitCode: ExitCode.SE,
|
code,
|
||||||
executionTime: null,
|
status: Status.SE,
|
||||||
memoryUsage: null,
|
userId,
|
||||||
};
|
problemId,
|
||||||
}
|
message: " Missing editor or docker configuration",
|
||||||
|
},
|
||||||
const problem = await prisma.problem.findUnique({
|
});
|
||||||
where: { id: problemId },
|
return submission;
|
||||||
select: {
|
|
||||||
timeLimit: true,
|
|
||||||
memoryLimit: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!problem) {
|
|
||||||
return {
|
|
||||||
id: uuid(),
|
|
||||||
output: "Problem not found.",
|
|
||||||
exitCode: ExitCode.SE,
|
|
||||||
executionTime: null,
|
|
||||||
memoryUsage: null,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -119,35 +130,78 @@ export async function judge(
|
|||||||
const file = `${fileName}.${fileExtension}`;
|
const file = `${fileName}.${fileExtension}`;
|
||||||
|
|
||||||
// Prepare the environment and create a container
|
// Prepare the environment and create a container
|
||||||
await prepareEnvironment(image, tag);
|
if (await prepareEnvironment(image, tag)) {
|
||||||
container = await createContainer(image, tag, workingDir, problem.memoryLimit);
|
container = await createContainer(image, tag, workingDir, problem.memoryLimit);
|
||||||
|
} else {
|
||||||
|
submission = await prisma.submission.create({
|
||||||
|
data: {
|
||||||
|
language,
|
||||||
|
code,
|
||||||
|
status: Status.SE,
|
||||||
|
userId,
|
||||||
|
problemId,
|
||||||
|
message: "The docker environment is not ready",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return submission;
|
||||||
|
}
|
||||||
|
|
||||||
|
submission = await prisma.submission.create({
|
||||||
|
data: {
|
||||||
|
language,
|
||||||
|
code,
|
||||||
|
status: Status.PD,
|
||||||
|
userId,
|
||||||
|
problemId,
|
||||||
|
message: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Upload code to the container
|
// Upload code to the container
|
||||||
const tarStream = createTarStream(file, value);
|
const tarStream = createTarStream(file, code);
|
||||||
await container.putArchive(tarStream, { path: workingDir });
|
await container.putArchive(tarStream, { path: workingDir });
|
||||||
|
|
||||||
// Compile the code
|
// Compile the code
|
||||||
const compileResult = await compile(container, file, fileName, compileOutputLimit);
|
const compileResult = await compile(container, file, fileName, compileOutputLimit, submission.id);
|
||||||
if (compileResult.exitCode === ExitCode.CE) {
|
if (compileResult.status === Status.CE) {
|
||||||
return compileResult;
|
return compileResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the code
|
// Run the code
|
||||||
const runResult = await run(container, fileName, problem.timeLimit, runOutputLimit);
|
const runResult = await run(container, fileName, problem.timeLimit, runOutputLimit, submission.id);
|
||||||
return runResult;
|
return runResult;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return {
|
if (submission) {
|
||||||
id: uuid(),
|
const updatedSubmission = await prisma.submission.update({
|
||||||
output: "System Error",
|
where: { id: submission.id },
|
||||||
exitCode: ExitCode.SE,
|
data: {
|
||||||
executionTime: null,
|
status: Status.SE,
|
||||||
memoryUsage: null,
|
message: "System Error",
|
||||||
};
|
}
|
||||||
|
})
|
||||||
|
return updatedSubmission;
|
||||||
|
} else {
|
||||||
|
submission = await prisma.submission.create({
|
||||||
|
data: {
|
||||||
|
language,
|
||||||
|
code,
|
||||||
|
status: Status.PD,
|
||||||
|
userId,
|
||||||
|
problemId,
|
||||||
|
message: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return submission;
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (container) {
|
if (container) {
|
||||||
await container.kill();
|
try {
|
||||||
await container.remove();
|
await container.kill();
|
||||||
|
await container.remove();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Container cleanup failed:", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,18 +210,19 @@ async function compile(
|
|||||||
container: Docker.Container,
|
container: Docker.Container,
|
||||||
file: string,
|
file: string,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
maxOutput: number = 1 * 1024 * 1024
|
compileOutputLimit: number = 1 * 1024 * 1024,
|
||||||
): Promise<JudgeResult> {
|
submissionId: string,
|
||||||
|
): Promise<Submission> {
|
||||||
const compileExec = await container.exec({
|
const compileExec = await container.exec({
|
||||||
Cmd: ["gcc", "-O2", file, "-o", fileName],
|
Cmd: ["gcc", "-O2", file, "-o", fileName],
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<JudgeResult>((resolve, reject) => {
|
return new Promise<Submission>((resolve, reject) => {
|
||||||
compileExec.start({}, (error, stream) => {
|
compileExec.start({}, (error, stream) => {
|
||||||
if (error || !stream) {
|
if (error || !stream) {
|
||||||
return reject({ output: "System Error", exitCode: ExitCode.SE });
|
return reject({ message: "System Error", Status: Status.SE });
|
||||||
}
|
}
|
||||||
|
|
||||||
const stdoutChunks: string[] = [];
|
const stdoutChunks: string[] = [];
|
||||||
@ -175,10 +230,10 @@ async function compile(
|
|||||||
const stdoutStream = new Writable({
|
const stdoutStream = new Writable({
|
||||||
write(chunk, _encoding, callback) {
|
write(chunk, _encoding, callback) {
|
||||||
let text = chunk.toString();
|
let text = chunk.toString();
|
||||||
if (stdoutLength + text.length > maxOutput) {
|
if (stdoutLength + text.length > compileOutputLimit) {
|
||||||
text = text.substring(0, maxOutput - stdoutLength);
|
text = text.substring(0, compileOutputLimit - stdoutLength);
|
||||||
stdoutChunks.push(text);
|
stdoutChunks.push(text);
|
||||||
stdoutLength = maxOutput;
|
stdoutLength = compileOutputLimit;
|
||||||
callback();
|
callback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -193,10 +248,10 @@ async function compile(
|
|||||||
const stderrStream = new Writable({
|
const stderrStream = new Writable({
|
||||||
write(chunk, _encoding, callback) {
|
write(chunk, _encoding, callback) {
|
||||||
let text = chunk.toString();
|
let text = chunk.toString();
|
||||||
if (stderrLength + text.length > maxOutput) {
|
if (stderrLength + text.length > compileOutputLimit) {
|
||||||
text = text.substring(0, maxOutput - stderrLength);
|
text = text.substring(0, compileOutputLimit - stderrLength);
|
||||||
stderrChunks.push(text);
|
stderrChunks.push(text);
|
||||||
stderrLength = maxOutput;
|
stderrLength = compileOutputLimit;
|
||||||
callback();
|
callback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -213,31 +268,31 @@ async function compile(
|
|||||||
const stderr = stderrChunks.join("");
|
const stderr = stderrChunks.join("");
|
||||||
const exitCode = (await compileExec.inspect()).ExitCode;
|
const exitCode = (await compileExec.inspect()).ExitCode;
|
||||||
|
|
||||||
let result: JudgeResult;
|
let updatedSubmission: Submission;
|
||||||
|
|
||||||
if (exitCode !== 0 || stderr) {
|
if (exitCode !== 0 || stderr) {
|
||||||
result = {
|
updatedSubmission = await prisma.submission.update({
|
||||||
id: uuid(),
|
where: { id: submissionId },
|
||||||
output: stderr || "Compilation Error",
|
data: {
|
||||||
exitCode: ExitCode.CE,
|
status: Status.CE,
|
||||||
executionTime: null,
|
message: stderr || "Compilation Error",
|
||||||
memoryUsage: null,
|
},
|
||||||
};
|
});
|
||||||
} else {
|
} else {
|
||||||
result = {
|
updatedSubmission = await prisma.submission.update({
|
||||||
id: uuid(),
|
where: { id: submissionId },
|
||||||
output: stdout,
|
data: {
|
||||||
exitCode: ExitCode.CS,
|
status: Status.CS,
|
||||||
executionTime: null,
|
message: stdout,
|
||||||
memoryUsage: null,
|
},
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(result);
|
resolve(updatedSubmission);
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.on("error", () => {
|
stream.on("error", () => {
|
||||||
reject({ output: "System Error", exitCode: ExitCode.SE });
|
reject({ message: "System Error", Status: Status.SE });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -249,7 +304,8 @@ async function run(
|
|||||||
fileName: string,
|
fileName: string,
|
||||||
timeLimit: number = 1000,
|
timeLimit: number = 1000,
|
||||||
maxOutput: number = 1 * 1024 * 1024,
|
maxOutput: number = 1 * 1024 * 1024,
|
||||||
): Promise<JudgeResult> {
|
submissionId: string,
|
||||||
|
): Promise<Submission> {
|
||||||
const runExec = await container.exec({
|
const runExec = await container.exec({
|
||||||
Cmd: [`./${fileName}`],
|
Cmd: [`./${fileName}`],
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
@ -257,7 +313,7 @@ async function run(
|
|||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise<JudgeResult>((resolve, reject) => {
|
return new Promise<Submission>((resolve, reject) => {
|
||||||
const stdoutChunks: string[] = [];
|
const stdoutChunks: string[] = [];
|
||||||
let stdoutLength = 0;
|
let stdoutLength = 0;
|
||||||
const stdoutStream = new Writable({
|
const stdoutStream = new Writable({
|
||||||
@ -297,7 +353,7 @@ async function run(
|
|||||||
// Start the exec stream
|
// Start the exec stream
|
||||||
runExec.start({ hijack: true }, (error, stream) => {
|
runExec.start({ hijack: true }, (error, stream) => {
|
||||||
if (error || !stream) {
|
if (error || !stream) {
|
||||||
return reject({ output: "System Error", exitCode: ExitCode.SE });
|
return reject({ message: "System Error", status: Status.SE });
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.write("[2,7,11,15]\n9\n[3,2,4]\n6\n[3,3]\n6");
|
stream.write("[2,7,11,15]\n9\n[3,2,4]\n6\n[3,3]\n6");
|
||||||
@ -307,13 +363,14 @@ async function run(
|
|||||||
|
|
||||||
// Timeout mechanism
|
// Timeout mechanism
|
||||||
const timeoutId = setTimeout(async () => {
|
const timeoutId = setTimeout(async () => {
|
||||||
resolve({
|
const updatedSubmission = await prisma.submission.update({
|
||||||
id: uuid(),
|
where: { id: submissionId },
|
||||||
output: "Time Limit Exceeded",
|
data: {
|
||||||
exitCode: ExitCode.TLE,
|
status: Status.TLE,
|
||||||
executionTime: null,
|
message: "Time Limit Exceeded",
|
||||||
memoryUsage: null,
|
}
|
||||||
});
|
})
|
||||||
|
resolve(updatedSubmission);
|
||||||
}, timeLimit);
|
}, timeLimit);
|
||||||
|
|
||||||
stream.on("end", async () => {
|
stream.on("end", async () => {
|
||||||
@ -322,41 +379,40 @@ async function run(
|
|||||||
const stderr = stderrChunks.join("");
|
const stderr = stderrChunks.join("");
|
||||||
const exitCode = (await runExec.inspect()).ExitCode;
|
const exitCode = (await runExec.inspect()).ExitCode;
|
||||||
|
|
||||||
let result: JudgeResult;
|
let updatedSubmission: Submission;
|
||||||
|
|
||||||
// Exit code 0 means successful execution
|
// Exit code 0 means successful execution
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
result = {
|
updatedSubmission = await prisma.submission.update({
|
||||||
id: uuid(),
|
where: { id: submissionId },
|
||||||
output: stdout,
|
data: {
|
||||||
exitCode: ExitCode.AC,
|
status: Status.AC,
|
||||||
executionTime: null,
|
message: stdout,
|
||||||
memoryUsage: null,
|
}
|
||||||
};
|
})
|
||||||
} else if (exitCode === 137) {
|
} else if (exitCode === 137) {
|
||||||
result = {
|
updatedSubmission = await prisma.submission.update({
|
||||||
id: uuid(),
|
where: { id: submissionId },
|
||||||
output: stderr || "Memory Limit Exceeded",
|
data: {
|
||||||
exitCode: ExitCode.MLE,
|
status: Status.MLE,
|
||||||
executionTime: null,
|
message: stderr || "Memory Limit Exceeded",
|
||||||
memoryUsage: null,
|
}
|
||||||
};
|
})
|
||||||
} else {
|
} else {
|
||||||
result = {
|
updatedSubmission = await prisma.submission.update({
|
||||||
id: uuid(),
|
where: { id: submissionId },
|
||||||
output: stderr || "Runtime Error",
|
data: {
|
||||||
exitCode: ExitCode.RE,
|
status: Status.RE,
|
||||||
executionTime: null,
|
message: stderr || "Runtime Error",
|
||||||
memoryUsage: null,
|
}
|
||||||
};
|
})
|
||||||
}
|
}
|
||||||
|
resolve(updatedSubmission);
|
||||||
resolve(result);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.on("error", () => {
|
stream.on("error", () => {
|
||||||
clearTimeout(timeoutId); // Clear timeout in case of error
|
clearTimeout(timeoutId); // Clear timeout in case of error
|
||||||
reject({ output: "System Error", exitCode: ExitCode.SE });
|
reject({ message: "System Error", Status: Status.SE });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user