Skip to content

Commit

Permalink
Merge branch 'main' into t1149-refactor-sandbox
Browse files Browse the repository at this point in the history
  • Loading branch information
jspark2000 authored Feb 4, 2025
2 parents 94e072c + db1c374 commit 9d45fa5
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 16 deletions.
52 changes: 44 additions & 8 deletions apps/backend/apps/client/src/submission/submission-sub.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class SubmissionSubscriptionService implements OnModuleInit {
raw.properties.type === RUN_MESSAGE_TYPE ||
raw.properties.type === USER_TESTCASE_MESSAGE_TYPE
) {
const testRequestedUserId = res.submissionId // test용 submissionId == test를 요청한 userId
const testRequestedUserId = res.submissionId
await this.handleRunMessage(
res,
testRequestedUserId,
Expand Down Expand Up @@ -90,21 +90,34 @@ export class SubmissionSubscriptionService implements OnModuleInit {

async handleRunMessage(
msg: JudgerResponse,
userId: number,
submissionId: number,
isUserTest = false
): Promise<void> {
const status = Status(msg.resultCode)
const testcaseId = msg.judgeResult?.testcaseId
const output = this.parseError(msg, status)
if (
status === ResultStatus.ServerError ||
status === ResultStatus.CompileError
) {
await this.handleJudgeError(status, msg)
return
}

if (!msg.judgeResult) {
throw new UnprocessableDataException('judgeResult is empty')
}
if (!testcaseId) {
const key = isUserTest ? userTestcasesKey(userId) : testcasesKey(userId)
const key = isUserTest
? userTestcasesKey(submissionId)
: testcasesKey(submissionId)
const testcaseIds = (await this.cacheManager.get<number[]>(key)) ?? []

for (const testcaseId of testcaseIds) {
await this.cacheManager.set(
isUserTest
? userTestKey(userId, testcaseId)
: testKey(userId, testcaseId),
? userTestKey(submissionId, testcaseId)
: testKey(submissionId, testcaseId),
{
id: testcaseId,
result: status,
Expand All @@ -115,10 +128,9 @@ export class SubmissionSubscriptionService implements OnModuleInit {
}
return
}

const key = isUserTest
? userTestKey(userId, testcaseId)
: testKey(userId, testcaseId)
? userTestKey(submissionId, testcaseId)
: testKey(submissionId, testcaseId)

const testcase = await this.cacheManager.get<{
id: number
Expand All @@ -131,6 +143,30 @@ export class SubmissionSubscriptionService implements OnModuleInit {
testcase.output = output
}

const cpuTime = BigInt(msg.judgeResult.cpuTime)
const memoryUsage = msg.judgeResult.memory

const testSubmission = await this.prisma.testSubmission.findUnique({
where: { id: submissionId }
})
if (testSubmission) {
const maxCpuTime = testSubmission.maxCpuTime || BigInt(0)
const newMaxCpuTime =
cpuTime > BigInt(maxCpuTime) ? cpuTime : BigInt(maxCpuTime)

const maxMemoryUsage = testSubmission.maxMemoryUsage || 0
const newMaxMemoryUsage =
memoryUsage > maxMemoryUsage ? memoryUsage : maxMemoryUsage

await this.prisma.testSubmission.update({
where: { id: testSubmission.id },
data: {
maxCpuTime: newMaxCpuTime,
maxMemoryUsage: newMaxMemoryUsage
}
})
}

await this.cacheManager.set(key, testcase, TEST_SUBMISSION_EXPIRE_TIME)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,14 @@ export class SubmissionController {
@Post('test')
async submitTest(
@Req() req: AuthenticatedRequest,
@Headers('x-forwarded-for') userIp: string,
@Query('problemId', new RequiredIntPipe('problemId')) problemId: number,
@Body() submissionDto: CreateSubmissionDto
) {
return await this.submissionService.submitTest(
req.user.id,
problemId,
userIp,
submissionDto
)
}
Expand All @@ -124,12 +126,14 @@ export class SubmissionController {
@Post('user-test')
async submitUserTest(
@Req() req: AuthenticatedRequest,
@Headers('x-forwarded-for') userIp: string,
@Query('problemId', new RequiredIntPipe('problemId')) problemId: number,
@Body() userTestSubmissionDto: CreateUserTestSubmissionDto
) {
return await this.submissionService.submitTest(
req.user.id,
problemId,
userIp,
userTestSubmissionDto,
true
)
Expand Down
88 changes: 80 additions & 8 deletions apps/backend/apps/client/src/submission/submission.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import {
Language,
Problem,
Role,
Prisma
Prisma,
TestSubmission
} from '@prisma/client'
import { AxiosRequestConfig } from 'axios'
import { Cache } from 'cache-manager'
import { plainToInstance } from 'class-transformer'
import { Request } from 'express'
import { Span } from 'nestjs-otel'
import {
testKey,
Expand Down Expand Up @@ -43,6 +45,7 @@ import { SubmissionPublicationService } from './submission-pub.service'
@Injectable()
export class SubmissionService {
private readonly logger = new Logger(SubmissionService.name)
private req: Request | undefined

constructor(
private readonly prisma: PrismaService,
Expand All @@ -52,6 +55,10 @@ export class SubmissionService {
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache
) {}

// setRequest(req: Request) {
// this.req = req
// }

/**
* 아직 채점되지 않은 제출 기록을 만들고, 채점 요청 큐에 메세지를 발행합니다.
*
Expand Down Expand Up @@ -626,6 +633,7 @@ export class SubmissionService {
async submitTest(
userId: number,
problemId: number,
userIp: string,
submissionDto: CreateSubmissionDto,
isUserTest = false
): Promise<void> {
Expand Down Expand Up @@ -663,7 +671,7 @@ export class SubmissionService {
result: 'Judging',
score: 0,
userId,
userIp: null,
userIp,
assignmentId: null,
contestId: null,
workbookId: null,
Expand Down Expand Up @@ -720,17 +728,20 @@ export class SubmissionService {
}
})

const testSubmissionId = (
await this.createTestSubmission(testSubmission, code, false)
).id
const testcaseIds: number[] = []
for (const rawTestcase of rawTestcases) {
await this.cacheManager.set(
testKey(userId, rawTestcase.id),
testKey(testSubmissionId, rawTestcase.id),
{ id: rawTestcase.id, result: 'Judging' },
TEST_SUBMISSION_EXPIRE_TIME
)
testcaseIds.push(rawTestcase.id)
}
await this.cacheManager.set(testcasesKey(userId), testcaseIds)

await this.cacheManager.set(testcasesKey(testSubmissionId), testcaseIds)
testSubmission.id = testSubmissionId // 위에서 구분을 위해 userId로 지정했던 id를 testSubmissionId로 변경
await this.publish.publishJudgeRequestMessage(code, testSubmission, true)
}

Expand Down Expand Up @@ -758,16 +769,20 @@ export class SubmissionService {
userTestcases: { id: number; in: string; out: string }[]
): Promise<void> {
const testcaseIds: number[] = []

const testSubmissionId = (
await this.createTestSubmission(testSubmission, code, true)
).id
for (const testcase of userTestcases) {
await this.cacheManager.set(
userTestKey(userId, testcase.id),
userTestKey(testSubmissionId, testcase.id),
{ id: testcase.id, result: 'Judging' },
TEST_SUBMISSION_EXPIRE_TIME
)
testcaseIds.push(testcase.id)
}
await this.cacheManager.set(userTestcasesKey(userId), testcaseIds)

await this.cacheManager.set(userTestcasesKey(testSubmissionId), testcaseIds)
testSubmission.id = testSubmissionId
await this.publish.publishJudgeRequestMessage(
code,
testSubmission,
Expand All @@ -777,6 +792,63 @@ export class SubmissionService {
)
}

@Span()
async createTestSubmission(
testSubmission: Submission,
codeSnippet: Snippet[],
isUserTest = false
): Promise<TestSubmission> {
const problem = await this.prisma.problem.findFirst({
where: {
id: testSubmission.problemId
}
})

if (!problem) {
throw new EntityNotExistException('Problem')
}

if (!problem.languages.includes(testSubmission.language)) {
throw new ConflictFoundException(
`This problem does not support language ${testSubmission.language}`
)
}
if (
!this.isValidCode(
codeSnippet,
testSubmission.language,
plainToInstance(Template, problem.template)
)
) {
throw new ConflictFoundException('Modifying template is not allowed')
}

const submissionData = {
code: codeSnippet.map((snippet) => ({ ...snippet })),
userId: testSubmission.userId,
userIp: testSubmission.userIp,
problemId: testSubmission.problemId,
codeSize: new TextEncoder().encode(codeSnippet[0].text).length,
language: testSubmission.language
}

try {
const submission = await this.prisma.testSubmission.create({
data: {
...submissionData,
isUserTest
}
})

return submission
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new UnprocessableDataException('Failed to create submission')
}
throw error
}
}

async getTestResult(userId: number, isUserTest = false) {
const testCasesKey = isUserTest
? userTestcasesKey(userId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
-- CreateTable
CREATE TABLE "test_submission" (
"id" SERIAL NOT NULL,
"user_id" INTEGER,
"user_ip" TEXT,
"problem_id" INTEGER NOT NULL,
"assignment_id" INTEGER,
"contest_id" INTEGER,
"workbook_id" INTEGER,
"language" "Language" NOT NULL,
"code" JSONB[],
"code_size" INTEGER,
"is_user_test" BOOLEAN NOT NULL DEFAULT false,
"max_cpu_time" BIGINT,
"max_memory_usage" INTEGER,
"create_time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"update_time" TIMESTAMP(3) NOT NULL,

CONSTRAINT "test_submission_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "test_submission" ADD CONSTRAINT "test_submission_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "test_submission" ADD CONSTRAINT "test_submission_problem_id_fkey" FOREIGN KEY ("problem_id") REFERENCES "problem"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "test_submission" ADD CONSTRAINT "test_submission_assignment_id_fkey" FOREIGN KEY ("assignment_id") REFERENCES "assignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "test_submission" ADD CONSTRAINT "test_submission_contest_id_fkey" FOREIGN KEY ("contest_id") REFERENCES "contest"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "test_submission" ADD CONSTRAINT "test_submission_workbook_id_fkey" FOREIGN KEY ("workbook_id") REFERENCES "workbook"("id") ON DELETE SET NULL ON UPDATE CASCADE;
37 changes: 37 additions & 0 deletions apps/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ model User {
useroauth UserOAuth?
CodeDraft CodeDraft[]
Image Image[]
testSubmission TestSubmission[]
@@map("user")
}
Expand Down Expand Up @@ -195,6 +196,7 @@ model Problem {
submission Submission[]
announcement Announcement[]
codeDraft CodeDraft[]
testSubmission TestSubmission[]
@@map("problem")
}
Expand Down Expand Up @@ -284,6 +286,7 @@ model Assignment {
assignmentRecord AssignmentRecord[]
submission Submission[]
announcement Announcement[]
testSubmission TestSubmission[]
@@map("assignment")
}
Expand Down Expand Up @@ -355,6 +358,7 @@ model Contest {
contestRecord ContestRecord[]
submission Submission[]
announcement Announcement[]
testSubmission TestSubmission[]
@@map("contest")
}
Expand Down Expand Up @@ -455,6 +459,7 @@ model Workbook {
workbookProblem WorkbookProblem[]
submission Submission[]
testSubmission TestSubmission[]
@@map("workbook")
}
Expand Down Expand Up @@ -558,3 +563,35 @@ model CodeDraft {
@@id(name: "codeDraftId", [userId, problemId])
@@map("code_draft")
}

model TestSubmission {
id Int @id @default(autoincrement())
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
userId Int? @map("user_id")
userIp String? @map("user_ip")
problem Problem @relation(fields: [problemId], references: [id], onDelete: Cascade)
problemId Int @map("problem_id")
assignment Assignment? @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
assignmentId Int? @map("assignment_id")
contest Contest? @relation(fields: [contestId], references: [id], onDelete: Cascade)
contestId Int? @map("contest_id")
workbook Workbook? @relation(fields: [workbookId], references: [id])
workbookId Int? @map("workbook_id")
/// code item structure
/// {
/// "id": number,
/// "text": string,
/// "locked": boolean
/// }
language Language
code Json[]
codeSize Int? @map("code_size")
isUserTest Boolean @default(false) @map("is_user_test")
maxCpuTime BigInt? @map("max_cpu_time")
maxMemoryUsage Int? @map("max_memory_usage")
createTime DateTime @default(now()) @map("create_time")
updateTime DateTime @updatedAt @map("update_time")
@@map("test_submission")
}

0 comments on commit 9d45fa5

Please sign in to comment.