diff --git a/apps/backend/apps/client/src/submission/class/judger-response.dto.ts b/apps/backend/apps/client/src/submission/class/judger-response.dto.ts index c42b515f0d..c7bc08d130 100644 --- a/apps/backend/apps/client/src/submission/class/judger-response.dto.ts +++ b/apps/backend/apps/client/src/submission/class/judger-response.dto.ts @@ -1,11 +1,13 @@ import { Type } from 'class-transformer' import { + IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, Max, - Min + Min, + ValidateNested } from 'class-validator' class JudgeResult { @@ -36,3 +38,14 @@ export class JudgerResponse { @IsOptional() judgeResult?: JudgeResult } + +export class SubmissionResponse { + @IsNumber() + @IsNotEmpty() + submissionId: number + + @IsNotEmpty() + @ValidateNested({ each: true }) + @Type(() => JudgerResponse) + judgeResults: JudgerResponse[] +} diff --git a/apps/backend/apps/client/src/submission/submission-sub.service.ts b/apps/backend/apps/client/src/submission/submission-sub.service.ts index ef0b78b89d..72e3706148 100644 --- a/apps/backend/apps/client/src/submission/submission-sub.service.ts +++ b/apps/backend/apps/client/src/submission/submission-sub.service.ts @@ -25,7 +25,7 @@ import { } from '@libs/constants' import { UnprocessableDataException } from '@libs/exception' import { PrismaService } from '@libs/prisma' -import { JudgerResponse } from './class/judger-response.dto' +import { JudgerResponse, SubmissionResponse } from './class/judger-response.dto' @Injectable() export class SubmissionSubscriptionService implements OnModuleInit { @@ -42,7 +42,7 @@ export class SubmissionSubscriptionService implements OnModuleInit { this.amqpService.setMessageHandlers({ onRunMessage: async (msg: object, isUserTest: boolean) => { try { - const res = await this.validateJudgerResponse(msg) + const res = await this.parseJudgerResponse(msg) await this.handleRunMessage(res, res.submissionId, isUserTest) } catch (error) { if ( @@ -60,10 +60,34 @@ export class SubmissionSubscriptionService implements OnModuleInit { }, onJudgeMessage: async (msg: object) => { try { - const res = await this.validateJudgerResponse(msg) + const res = await this.parseJudgerResponse(msg) - const isOudated = await this.isOutdatedTestcase(res) - if (isOudated) return + // JudgerResponse 메시지는 처리하지 않습니다. + return // Ack + } catch (error) { + if ( + Array.isArray(error) && + error.every((e) => e instanceof ValidationError) + ) { + this.logger.error(error, 'Message format error') + } else if (error instanceof UnprocessableDataException) { + this.logger.error(error, 'Iris exception') + } else { + this.logger.error(error, 'Unexpected error') + } + throw error // MQTT 서비스에서 Nack 처리 + } + }, + onSubmissionMessage: async (msg) => { + try { + const res = await this.parseSubmissionResponse(msg) + + const validResponse = await this.filterOutdatedTestcases( + res.submissionId, + res.judgeResults + ) + + res.judgeResults = validResponse await this.handleJudgerMessage(res) } catch (error) { @@ -216,7 +240,7 @@ export class SubmissionSubscriptionService implements OnModuleInit { * @throws {ValidationError[]} 유효성 검사 실패 시 발생 */ @Span() - async validateJudgerResponse(msg: object): Promise { + async parseJudgerResponse(msg: object): Promise { const res: JudgerResponse = plainToInstance(JudgerResponse, msg) await validateOrReject(res) @@ -224,36 +248,68 @@ export class SubmissionSubscriptionService implements OnModuleInit { } /** - * 채점 결과가 도착한 테스트케이스가 최신 상태인지(유효한지) 확인합니다. + * 채점 서버로부터 수신한 메시지의 형식을 검증합니다. + * + * 1. 수신한 `msg` 객체를 `SubmissionResponse` DTO 인스턴스로 변환합니다 (`plainToInstance`). + * 2. `class-validator`를 사용하여 데이터의 유효성을 검사합니다. + * 3. 검증 성공 시 DTO 인스턴스를 반환하며, 실패 시 예외를 던집니다. + * + * @param {object} msg 채점 서버로부터 수신한 Raw 메시지 객체 + * @returns {Promise} 유효성 검사가 완료된 `SubmissionResponse` 객체 + * @throws {ValidationError[]} 유효성 검사 실패 시 발생 + */ + async parseSubmissionResponse(msg: object): Promise { + const res: SubmissionResponse = plainToInstance(SubmissionResponse, msg) + await validateOrReject(res) + + return res + } + + /** + * 도착한 테스트케이스들이 최신 상태인지(유효한지) 확인합니다. * * 문제 출제자가 테스트케이스를 수정하거나 새로 업로드하면(`uploadTestcaseZip` 등), * 기존 테스트케이스들은 모두 `isOutdated: true`로 설정됩니다. * * 1. 응답에 포함된 `testcaseId`가 현재 유효한지(`isOutdated: false`) 확인합니다. - * 2. 해당 테스트케이스가 존재하지 않으면(즉, Outdated 되었거나 삭제된 경우), `true`를 반환합니다. + * 2. 해당 테스트케이스가 존재하지 않으면(즉, Outdated 되었거나 삭제된 경우), 반환값에서 제외합니다. * - * @param {JudgerResponse} res 채점 서버로부터 수신한 응답 메시지 객체 - * @returns {Promise} 테스트케이스가 만료(Outdated)되었으면 `true`, 유효하면 `false` + * @param {number} submissionId 보내진 응답의 제출 ID + * @param {JudgerResponse[]} res 채점 서버로부터 수신한 채점 결과 배열 + * @returns {Promise} 유효한 채점 결과만 담은 배열 */ @Span() - async isOutdatedTestcase(res: JudgerResponse): Promise { - const testcase = await this.prisma.problemTestcase.count({ + async filterOutdatedTestcases( + submissionId: number, + res: JudgerResponse[] + ): Promise { + const testCaseIds = res + .map((v) => v.judgeResult?.testcaseId) + .filter((v) => v !== undefined) + + const validTestcases = await this.prisma.problemTestcase.findMany({ + select: { id: true }, where: { - id: res.judgeResult?.testcaseId, + id: { in: testCaseIds }, isOutdated: false, problem: { submission: { - some: { id: res.submissionId } + some: { id: submissionId } } } } }) - return testcase === 0 + const validIds = new Set(validTestcases.map((v) => v.id)) + + return res.filter((v) => { + const id = v.judgeResult?.testcaseId + return id !== undefined && validIds.has(id) + }) } /** - * 채점 서버로부터 수신한 개별 테스트케이스의 채점 결과 메시지를 처리합니다. + * 채점 서버로부터 수신한 채점 결과 메시지를 처리합니다. * * 1. 메시지의 상태 코드(`resultCode`)를 파싱하여 `ResultStatus`를 결정합니다. * 2. 에러 상태(ServerError, CompileError)인 경우, `handleJudgeError`를 호출하여 예외 처리를 수행하고 종료합니다. @@ -265,33 +321,46 @@ export class SubmissionSubscriptionService implements OnModuleInit { * @throws {UnprocessableDataException} 정상 결과(`judgeResult`)가 누락된 경우 예외 발생 */ @Span() - async handleJudgerMessage(msg: JudgerResponse): Promise { - const status = Status(msg.resultCode) + async handleJudgerMessage(msg: SubmissionResponse): Promise { + const submissionResults: { + submissionId: number + problemTestcaseId: number + result: ResultStatus + cpuTime: bigint + memoryUsage: number + output: string | undefined + }[] = [] + + for (const value of msg.judgeResults) { + const status = Status(value.resultCode) + + if ( + status === ResultStatus.ServerError || + status === ResultStatus.CompileError + ) { + await this.handleJudgeError(status, value) + return + } - if ( - status === ResultStatus.ServerError || - status === ResultStatus.CompileError - ) { - await this.handleJudgeError(status, msg) - return - } + if (!value.judgeResult) { + throw new UnprocessableDataException( + `JudgeResult is missing for submission ${msg.submissionId} - cannot process judge response` + ) + } - if (!msg.judgeResult) { - throw new UnprocessableDataException( - 'JudgeResult is missing for submission ${msg.submissionId} - cannot process judge response' - ) - } + const submissionResult = { + submissionId: value.submissionId, + problemTestcaseId: value.judgeResult.testcaseId, + result: status, + cpuTime: BigInt(value.judgeResult.cpuTime), + memoryUsage: value.judgeResult.memory, + output: value.judgeResult.output + } - const submissionResult = { - submissionId: msg.submissionId, - problemTestcaseId: msg.judgeResult.testcaseId, - result: status, - cpuTime: BigInt(msg.judgeResult.cpuTime), - memoryUsage: msg.judgeResult.memory, - output: msg.judgeResult.output + submissionResults.push(submissionResult) } - await this.updateTestcaseJudgeResult(submissionResult) + await this.updateTestcaseJudgeResult(submissionResults) } /** @@ -343,7 +412,7 @@ export class SubmissionSubscriptionService implements OnModuleInit { * 개별 테스트케이스의 채점 결과를 DB에 반영하고, 후속 처리를 수행합니다. * * 1. `SubmissionResult` 테이블에 해당 테스트케이스의 채점 결과(성공 여부, 시간, 메모리, 출력 등)를 업데이트합니다. - * 2. 유효한 채점 결과(Judging, ServerError 등이 아닌 확정된 상태)라면, `updateTestcaseStats`를 호출하여 테스트케이스별 통계를 갱신합니다. + * 2. 유효한 채점 결과(Judging, ServerError 등이 아닌 확정된 상태)라면, 테스트케이스별 통계를 갱신합니다. * 3. `updateSubmissionResult`를 호출하여, 해당 제출(Submission)의 전체 채점 완료 여부를 확인하고 최종 결과를 갱신합니다. * * @param {Partial & Pick} submissionResult @@ -352,24 +421,12 @@ export class SubmissionSubscriptionService implements OnModuleInit { * */ @Span() async updateTestcaseJudgeResult( - submissionResult: Partial & - Pick + submissionResults: (Partial & + Pick)[] ): Promise { - await this.prisma.submissionResult.update({ - where: { - // eslint-disable-next-line @typescript-eslint/naming-convention - submissionId_problemTestcaseId: { - submissionId: submissionResult.submissionId, - problemTestcaseId: submissionResult.problemTestcaseId - } - }, - data: { - result: submissionResult.result, - cpuTime: submissionResult.cpuTime, - memoryUsage: submissionResult.memoryUsage, - output: submissionResult.output - } - }) + if (submissionResults.length === 0) return + + const submissionId = submissionResults[0].submissionId const invalidSubmissionStatuses: Array = [ ResultStatus.Judging, @@ -377,50 +434,59 @@ export class SubmissionSubscriptionService implements OnModuleInit { ResultStatus.Blind, ResultStatus.Canceled ] - if ( - invalidSubmissionStatuses.every( - (result) => result !== submissionResult.result - ) - ) { - this.updateTestcaseStats( - submissionResult.problemTestcaseId, - submissionResult.result === ResultStatus.Accepted - ) - } - await this.updateSubmissionResult(submissionResult.submissionId) - } - - /** - * 개별 테스트케이스의 실행 통계를 업데이트합니다. - * - * 매 실행 시마다 `submissionCount`를 1씩 증가시키며, - * 결과가 `Accepted`인 경우 `acceptedCount`도 1씩 증가시킵니다. - * - * @param {number} testcaseId 통계를 업데이트할 테스트케이스 ID - * @param {boolean} isAccepted 채점 결과가 정답(Accepted)인지 여부 - * @returns {Promise} - */ - @Span() - async updateTestcaseStats( - testcaseId: number, - isAccepted: boolean - ): Promise { - const testcaseStats = { - where: { - id: testcaseId - }, - data: { - submissionCount: { - increment: 1 - }, - acceptedCount: { - increment: isAccepted ? 1 : 0 - } - } - } + const statsTargets = submissionResults.filter( + (submissionResult) => + !invalidSubmissionStatuses.includes(submissionResult.result) + ) - await this.prisma.problemTestcase.update(testcaseStats) + await this.prisma.$transaction([ + this.prisma.$executeRaw` + UPDATE "submission_result" AS sr + SET "result" = v.result::"ResultStatus", + "cpu_time" = v.cpu_time, + "memory_usage" = v.memory_usage, + "output" = v.output, + "update_time" = NOW() + FROM ( + VALUES ${Prisma.join( + submissionResults.map( + (r) => Prisma.sql`( + ${r.problemTestcaseId}::int, + ${r.result}::text, + ${r.cpuTime ?? null}::bigint, + ${r.memoryUsage ?? null}::int, + ${r.output ?? null}::text + )` + ) + )} + ) AS v(problem_test_case_id, result, cpu_time, memory_usage, output) + WHERE sr."submission_id" = ${submissionId} + AND sr."problem_test_case_id" = v.problem_test_case_id; + `, + ...(statsTargets.length > 0 + ? [ + this.prisma.$executeRaw` + UPDATE "problem_testcase" as pt + SET "submission_count" = pt."submission_count" + 1, + "accepted_count" = pt."accepted_count" + v.accepted + FROM ( + VALUES ${Prisma.join( + statsTargets.map( + (r) => Prisma.sql`( + ${r.problemTestcaseId}::int, + ${r.result === ResultStatus.Accepted ? 1 : 0}::int + )` + ) + )} + ) AS v(problem_test_case_id, accepted) + WHERE pt."id" = v.problem_test_case_id + ` + ] + : []) + ]) + + await this.updateSubmissionResult(submissionId) } /** diff --git a/apps/backend/apps/client/src/submission/test/submission-sub.service.spec.ts b/apps/backend/apps/client/src/submission/test/submission-sub.service.spec.ts index da3fdcedee..b823ebe2f1 100644 --- a/apps/backend/apps/client/src/submission/test/submission-sub.service.spec.ts +++ b/apps/backend/apps/client/src/submission/test/submission-sub.service.spec.ts @@ -42,6 +42,11 @@ const msg = { judgeResult } +const submissionResponseMsg = { + submissionId: 1, + judgeResults: [msg] +} + const submission: Submission & { submissionResult: SubmissionResult[] } = { ...submissions[0], codeSize: 1000, @@ -126,8 +131,12 @@ const db = { findUnique: mockFunc, update: mockFunc }, - $transaction: async (fn: (prisma: typeof db) => Promise) => { - return fn(db) + $executeRaw: mockFunc, + $transaction: async (arg: unknown) => { + if (Array.isArray(arg)) { + return Promise.all(arg) + } + return (arg as (prisma: typeof db) => Promise)(db) } } @@ -174,13 +183,12 @@ describe('SubmissionSubscriptionService', () => { amqpService = module.get(JudgeAMQPService) cache = module.get(CACHE_MANAGER) sandbox.stub(cache, 'get').resolves([]) - sandbox - .stub(db, '$transaction') - .callsFake( - async (fn: (prisma: typeof db) => Promise): Promise => { - return fn(db) - } - ) + sandbox.stub(db, '$transaction').callsFake(async (arg: unknown) => { + if (Array.isArray(arg)) { + return Promise.all(arg) + } + return (arg as (prisma: typeof db) => Promise)(db) + }) }) afterEach(() => { @@ -206,9 +214,9 @@ describe('SubmissionSubscriptionService', () => { }) }) - describe('validateJudgerResponse', () => { + describe('parseJudgerResponse', () => { it('should return JudgerResponse', async () => { - const result = await service.validateJudgerResponse(msg) + const result = await service.parseJudgerResponse(msg) expect(result).to.be.deep.equal(msg) }) @@ -221,7 +229,7 @@ describe('SubmissionSubscriptionService', () => { judgeResult } - await expect(service.validateJudgerResponse(invalidMsg)).to.be.rejected + await expect(service.parseJudgerResponse(invalidMsg)).to.be.rejected }) }) @@ -333,16 +341,23 @@ describe('SubmissionSubscriptionService', () => { it('should resolve', async () => { const spy = sandbox.stub(service, 'updateTestcaseJudgeResult').resolves() - await expect(service.handleJudgerMessage(msg)).not.to.be.rejected + await expect(service.handleJudgerMessage(submissionResponseMsg)).not.to.be + .rejected expect( - spy.calledOnceWithExactly({ - submissionId: msg.submissionId, - problemTestcaseId: msg.judgeResult.testcaseId, - result: Status(msg.resultCode), - cpuTime: BigInt(msg.judgeResult.cpuTime), - memoryUsage: msg.judgeResult.memory, - output: undefined - }) + spy.calledOnceWithExactly([ + { + submissionId: submissionResponseMsg.submissionId, + problemTestcaseId: + submissionResponseMsg.judgeResults[0].judgeResult.testcaseId, + result: Status(submissionResponseMsg.judgeResults[0].resultCode), + cpuTime: BigInt( + submissionResponseMsg.judgeResults[0].judgeResult.cpuTime + ), + memoryUsage: + submissionResponseMsg.judgeResults[0].judgeResult.memory, + output: submissionResponseMsg.judgeResults[0].judgeResult.output + } + ]) ).to.be.true }) @@ -357,8 +372,11 @@ describe('SubmissionSubscriptionService', () => { error: '', judgeResult } - - await service.handleJudgerMessage(serverErrMsg) + const multiMsg = { + submissionId: 1, + judgeResults: [serverErrMsg, msg] + } + await service.handleJudgerMessage(multiMsg) expect(handlerSpy.calledOnceWith(ResultStatus.ServerError, serverErrMsg)) .to.be.true expect(updateSpy.notCalled).to.be.true @@ -369,18 +387,40 @@ describe('SubmissionSubscriptionService', () => { const updateSpy = sandbox .stub(service, 'updateTestcaseJudgeResult') .resolves() - const serverErrMsg = { + const compileErrMsg = { resultCode: 6, submissionId: 1, error: '', judgeResult } + const multiMsg = { + submissionId: 1, + judgeResults: [compileErrMsg, msg] + } - await service.handleJudgerMessage(serverErrMsg) - expect(handlerSpy.calledOnceWith(ResultStatus.CompileError, serverErrMsg)) - .to.be.true + await service.handleJudgerMessage(multiMsg) + expect( + handlerSpy.calledOnceWith(ResultStatus.CompileError, compileErrMsg) + ).to.be.true expect(updateSpy.notCalled).to.be.true }) + + it('should throw when judgeResult is missing', async () => { + const missingResultJudgeResponse = { + resultCode: 1, + submissionId: 1, + error: '' + } + + const missingResultMsg = { + submissionId: 1, + judgeResults: [missingResultJudgeResponse] + } + + await expect( + service.handleJudgerMessage(missingResultMsg) + ).to.be.rejectedWith(UnprocessableDataException) + }) }) describe('handleJudgeError', () => { @@ -948,37 +988,54 @@ describe('SubmissionSubscriptionService', () => { }) describe('updateTestcaseJudgeResult', () => { - it('should resolves', async () => { - const updateSpy = sandbox.stub(db.submissionResult, 'update').resolves() + it('should return early when submissionResults is empty', async () => { + const transactionSpy = sandbox.stub(db, '$transaction').resolves([]) const updateSubmissionResultSpy = sandbox .stub(service, 'updateSubmissionResult') .resolves() - await service.updateTestcaseJudgeResult(submissionResults[0]) + await service.updateTestcaseJudgeResult([]) + + expect(transactionSpy.notCalled).to.be.true + expect(updateSubmissionResultSpy.notCalled).to.be.true + }) + + it('should run both submission_result and problem_testcase batch updates when there are valid stats targets', async () => { + const executeRawSpy = sandbox.stub(db, '$executeRaw').resolves(1) + const transactionSpy = sandbox.stub(db, '$transaction').resolves([1, 1]) + const updateSubmissionResultSpy = sandbox + .stub(service, 'updateSubmissionResult') + .resolves() + + await service.updateTestcaseJudgeResult(submissionResults) + + expect(transactionSpy.calledOnce).to.be.true + const passedQueries = transactionSpy.firstCall.args[0] + expect(passedQueries).to.have.lengthOf(2) + expect(executeRawSpy.calledTwice).to.be.true - expect( - updateSpy.calledOnceWith({ - where: { - // eslint-disable-next-line @typescript-eslint/naming-convention - submissionId_problemTestcaseId: { - submissionId: submissionResults[0].submissionId, - problemTestcaseId: submissionResults[0].problemTestcaseId - } - }, - data: { - result: submissionResults[0].result, - cpuTime: submissionResults[0].cpuTime, - memoryUsage: submissionResults[0].memoryUsage, - output: null - } - }) - ).to.be.true expect( updateSubmissionResultSpy.calledOnceWith( submissionResults[0].submissionId ) ).to.be.true }) + + it('should skip the problem_testcase batch when all results are Judging/ServerError/Blind/Canceled', async () => { + const executeRawSpy = sandbox.stub(db, '$executeRaw').resolves(1) + const transactionSpy = sandbox.stub(db, '$transaction').resolves([1]) + sandbox.stub(service, 'updateSubmissionResult').resolves() + + const canceledOnly = [ + { ...submissionResults[0], result: ResultStatus.Canceled } + ] + + await service.updateTestcaseJudgeResult(canceledOnly) + + const passedQueries = transactionSpy.firstCall.args[0] + expect(passedQueries).to.have.lengthOf(1) + expect(executeRawSpy.calledOnce).to.be.true + }) }) describe('updateProblemAccepted', () => { diff --git a/apps/backend/libs/amqp/src/amqp.service.ts b/apps/backend/libs/amqp/src/amqp.service.ts index d55b2e9ee5..fe5549a1fa 100644 --- a/apps/backend/libs/amqp/src/amqp.service.ts +++ b/apps/backend/libs/amqp/src/amqp.service.ts @@ -22,7 +22,8 @@ import { MESSAGE_PRIORITY_HIGH, MESSAGE_PRIORITY_MIDDLE, MESSAGE_PRIORITY_LOW, - SUBMISSION_KEY + SUBMISSION_KEY, + SUBMISSION_MESSAGE_TYPE } from '@libs/constants' @Injectable() @@ -43,22 +44,20 @@ export class JudgeAMQPService { try { // 메시지 타입에 따라 적절한 핸들러로 라우팅 - if ( - raw.properties?.type === RUN_MESSAGE_TYPE || - raw.properties?.type === USER_TESTCASE_MESSAGE_TYPE - ) { - if (this.messageHandlers?.onRunMessage) { - await this.messageHandlers.onRunMessage( - msg, - raw.properties.type === USER_TESTCASE_MESSAGE_TYPE - ) - } - return - } + const type = raw.properties?.type + const handlerMap = this.getMessageHandlerMap() + + const handler = handlerMap[type] - if (this.messageHandlers?.onJudgeMessage) { - await this.messageHandlers.onJudgeMessage(msg) + if (!handler) { + this.logger.error( + `Unknown MessageType found: ${raw.properties?.type}` + ) + return new Nack(false) } + + await handler(msg) + return } catch (error) { this.logger.error(error, 'Unexpected error in message handler') return new Nack() @@ -145,6 +144,7 @@ export class JudgeAMQPService { setMessageHandlers(handlers: { onRunMessage?: (msg: object, isUserTest: boolean) => Promise onJudgeMessage?: (msg: object) => Promise + onSubmissionMessage?: (msg: object) => Promise }) { this.messageHandlers = handlers } @@ -152,6 +152,25 @@ export class JudgeAMQPService { private messageHandlers?: { onRunMessage?: (msg: object, isUserTest: boolean) => Promise onJudgeMessage?: (msg: object) => Promise + onSubmissionMessage?: (msg: object) => Promise + } + + private getMessageHandlerMap(): Record< + string, + ((msg: object) => Promise) | undefined + > { + const onRunMessage = this.messageHandlers?.onRunMessage + + return { + [RUN_MESSAGE_TYPE]: onRunMessage + ? (msg: object) => onRunMessage(msg, false) + : undefined, + [USER_TESTCASE_MESSAGE_TYPE]: onRunMessage + ? (msg: object) => onRunMessage(msg, true) + : undefined, + [JUDGE_MESSAGE_TYPE]: this.messageHandlers?.onJudgeMessage, + [SUBMISSION_MESSAGE_TYPE]: this.messageHandlers?.onSubmissionMessage + } } } diff --git a/apps/backend/libs/constants/src/rabbitmq.constants.ts b/apps/backend/libs/constants/src/rabbitmq.constants.ts index be6a0d68b4..49eca2f49d 100644 --- a/apps/backend/libs/constants/src/rabbitmq.constants.ts +++ b/apps/backend/libs/constants/src/rabbitmq.constants.ts @@ -11,6 +11,7 @@ export const RESULT_QUEUE = 'iris.q.judge.result' export const ORIGIN_HANDLER_NAME = 'codedang-handler' export const JUDGE_MESSAGE_TYPE = 'judge' +export const SUBMISSION_MESSAGE_TYPE = 'submission' export const RUN_MESSAGE_TYPE = 'run' export const USER_TESTCASE_MESSAGE_TYPE = 'userTestCase' diff --git a/apps/iris/src/router/response/judge.go b/apps/iris/src/router/response/judge.go index cbbed2a6e4..8d5a0ed734 100644 --- a/apps/iris/src/router/response/judge.go +++ b/apps/iris/src/router/response/judge.go @@ -11,7 +11,6 @@ type JudgeResponse struct { SubmissionId int `json:"submissionId"` JudgeResultCode taskerror.ResultCode `json:"resultCode"` JudgeResult json.RawMessage `json:"judgeResult"` - Finished bool `json:"finished"` Error string `json:"error"` } @@ -30,7 +29,6 @@ func NewJudgeResponse(id string, data json.RawMessage, err error) *JudgeResponse JudgeResultCode: resultCode, JudgeResult: data, Error: errMessage, - Finished: false, } } diff --git a/apps/iris/src/router/response/submission.go b/apps/iris/src/router/response/submission.go index 80e4011858..d21b41f2b3 100644 --- a/apps/iris/src/router/response/submission.go +++ b/apps/iris/src/router/response/submission.go @@ -7,7 +7,6 @@ import ( type SubmissionResponse struct { SubmissionId int `json:"submissionId"` JudgeResults []*JudgeResponse `json:"judgeResults"` - Finished bool `json:"finished"` } func NewSubmissionResponse(id string, judgeResponses []*JudgeResponse) *SubmissionResponse { @@ -17,7 +16,6 @@ func NewSubmissionResponse(id string, judgeResponses []*JudgeResponse) *Submissi return &SubmissionResponse{ SubmissionId: _id, JudgeResults: judgeResponses, - Finished: true, } }