Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

public record TestcaseResultPayload(

int seqId,
Long testcaseId,

boolean isPassed,

Expand All @@ -17,9 +17,9 @@ public record TestcaseResultPayload(
String message

) {
public static TestcaseResultPayload fromEvaluation(int seqId, boolean isPassed, JudgeResult result) {
public static TestcaseResultPayload fromEvaluation(Long testcaseId, boolean isPassed, JudgeResult result) {
return new TestcaseResultPayload(
seqId,
testcaseId,
isPassed,
result.actualOutput(),
result.executionTime(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package org.ezcode.codetest.application.submission.dto.request.compile;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

import org.ezcode.codetest.application.submission.model.SubmissionContext;

public record CodeCompileRequest(
Expand All @@ -14,14 +11,11 @@ public record CodeCompileRequest(
String stdin

) {
public static CodeCompileRequest of(int seqId, SubmissionContext ctx) {
public static CodeCompileRequest of(int index, SubmissionContext ctx) {
return new CodeCompileRequest(
ctx.getSourceCode(),
ctx.getJudge0Id(),
ctx.getInput(seqId));
ctx.getInput(index));
}

private static String encodeBase64(String str) {
return Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.ezcode.codetest.application.submission.dto.response.submission;

import java.util.List;

import org.ezcode.codetest.domain.problem.model.entity.Testcase;

public record SubmitResponse(

String sessionKey,

List<Long> testcaseIds

) {
public static SubmitResponse of(String sessionKey, List<Testcase> testcases) {
return new SubmitResponse(sessionKey, testcases.stream().map(Testcase::getId).toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,16 @@ public long getJudge0Id() {
return language.getJudge0Id();
}

public String getInput(int seqId) {
return getTestcases().get(seqId - 1).getInput();
public String getInput(int index) {
return getTestcases().get(index).getInput();
}

public String getExpectedOutput(int seqId) {
return getTestcases().get(seqId - 1).getOutput();
public String getExpectedOutput(int index) {
return getTestcases().get(index).getOutput();
}

public Long getTestcaseId(int index) {
return getTestcases().get(index).getId();
}

public long getTimeLimit() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ public void publishInitTestcases(SubmissionContext ctx) {
}

public void runTestcases(SubmissionContext ctx) throws InterruptedException {
IntStream.rangeClosed(1, ctx.getTestcaseCount())
.forEach(seqId -> runTestcaseAsync(seqId, ctx));

IntStream.range(0, ctx.getTestcaseCount())
.forEach(i -> runTestcaseAsync(i, ctx));

if (!ctx.latch().await(100, TimeUnit.SECONDS)) {
throw new SubmissionException(SubmissionExceptionCode.TESTCASE_TIMEOUT);
Expand All @@ -66,18 +67,18 @@ public void publishSubmissionError(String sessionKey, Exception e) {
submissionEventService.publishSubmissionError(new SubmissionErrorEvent(sessionKey, e));
}

private void runTestcaseAsync(int seqId, SubmissionContext ctx) {
private void runTestcaseAsync(int index, SubmissionContext ctx) {
CompletableFuture.runAsync(() -> {
try {
log.info("[Judge RUN] Thread = {}", Thread.currentThread().getName());
String token = judgeClient.submitAndGetToken(CodeCompileRequest.of(seqId, ctx));
String token = judgeClient.submitAndGetToken(CodeCompileRequest.of(index, ctx));
JudgeResult result = judgeClient.pollUntilDone(token);

TestcaseEvaluationInput input = TestcaseEvaluationInput.from(result, ctx, seqId);
TestcaseEvaluationInput input = TestcaseEvaluationInput.from(result, ctx, index);

boolean isPassed = submissionDomainService.handleEvaluationAndUpdateStats(input, ctx);

publishTestcaseUpdate(seqId, ctx, isPassed, result);
publishTestcaseUpdate(index, ctx, isPassed, result);
} catch (Exception e) {
if (ctx.notified().compareAndSet(false, true)) {
publishSubmissionError(ctx.getSessionKey(), e);
Expand All @@ -89,9 +90,9 @@ private void runTestcaseAsync(int seqId, SubmissionContext ctx) {
}, judgeTestcaseExecutor);
}

private void publishTestcaseUpdate(int seqId, SubmissionContext ctx, boolean isPassed, JudgeResult result) {
private void publishTestcaseUpdate(int index, SubmissionContext ctx, boolean isPassed, JudgeResult result) {
submissionEventService.publishTestcaseUpdate(TestcaseEvaluatedEvent.of(
ctx, TestcaseResultPayload.fromEvaluation(seqId, isPassed, result))
ctx, TestcaseResultPayload.fromEvaluation(ctx.getTestcaseId(index), isPassed, result))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
import org.ezcode.codetest.application.submission.dto.request.review.ReviewPayload;
import org.ezcode.codetest.application.submission.dto.response.review.CodeReviewResponse;
import org.ezcode.codetest.application.submission.dto.response.submission.GroupedSubmissionResponse;
import org.ezcode.codetest.application.submission.dto.response.submission.SubmitResponse;
import org.ezcode.codetest.application.submission.model.ReviewResult;
import org.ezcode.codetest.application.submission.model.SubmissionContext;
import org.ezcode.codetest.application.submission.port.ExceptionNotifier;
import org.ezcode.codetest.application.submission.port.LockManager;
import org.ezcode.codetest.application.submission.port.QueueProducer;
import org.ezcode.codetest.domain.problem.model.entity.Testcase;
import org.ezcode.codetest.domain.problem.service.TestcaseDomainService;
import org.ezcode.codetest.domain.submission.exception.SubmissionException;
import org.ezcode.codetest.domain.submission.exception.code.SubmissionExceptionCode;
import org.ezcode.codetest.infrastructure.event.dto.submission.SubmissionMessage;
Expand Down Expand Up @@ -43,6 +46,7 @@ public class SubmissionService {
private final ReviewClient reviewClient;
private final UserDomainService userDomainService;
private final ProblemDomainService problemDomainService;
private final TestcaseDomainService testcaseDomainService;
private final LanguageDomainService languageDomainService;
private final SubmissionDomainService submissionDomainService;
private final QueueProducer queueProducer;
Expand All @@ -51,19 +55,21 @@ public class SubmissionService {
private final JudgementService judgementService;
private final GitHubPushService gitHubPushService;

public String enqueueCodeSubmission(Long problemId, CodeSubmitRequest request, AuthUser authUser) {
public SubmitResponse enqueueCodeSubmission(Long problemId, CodeSubmitRequest request, AuthUser authUser) {

boolean acquired = lockManager.tryLock("submission", authUser.getId(), problemId);
if (!acquired) {
throw new SubmissionException(SubmissionExceptionCode.ALREADY_JUDGING);
}

String sessionKey = authUser.getId() + "_" + UUID.randomUUID();
List<Testcase> testcaseList = testcaseDomainService.getTestcaseListByProblemId(problemId);

queueProducer.enqueue(
new SubmissionMessage(sessionKey, problemId, request.languageId(), authUser.getId(), request.sourceCode())
);

return sessionKey;
return SubmitResponse.of(sessionKey, testcaseList);
}

@Async("judgeSubmissionExecutor")
Expand All @@ -73,7 +79,6 @@ public void processSubmissionAsync(SubmissionMessage msg) {
log.info("[Submission RUN] Thread = {}", Thread.currentThread().getName());
log.info("[큐 수신] SubmissionMessage.sessionKey: {}", msg.sessionKey());
SubmissionContext ctx = createSubmissionContext(msg);
judgementService.publishInitTestcases(ctx);
judgementService.runTestcases(ctx);
judgementService.finalizeAndPublish(ctx);
gitHubPushService.pushSolutionToRepo(ctx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public interface TestcaseRepository {

List<Testcase> findAllByProblem(Problem problem);

List<Testcase> findAllByProblemId(Long problemId);

Testcase findByTestcase(Long testcaseId);

void delete(Testcase testcase);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public List<Testcase> getTestcaseList(Problem problem) {
return testcaseRepository.findAllByProblem(problem);
}

public List<Testcase> getTestcaseListByProblemId(Long problemId) {
return testcaseRepository.findAllByProblemId(problemId);
}

public Testcase getTestcase(Long testcaseId) {
return testcaseRepository.findByTestcase(testcaseId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ public record TestcaseEvaluationInput(
long memoryLimit

) {
public static TestcaseEvaluationInput from(JudgeResult result, SubmissionContext ctx, int seqId) {
public static TestcaseEvaluationInput from(JudgeResult result, SubmissionContext ctx, int index) {
return new TestcaseEvaluationInput(
ctx.getExpectedOutput(seqId),
ctx.getExpectedOutput(index),
result.actualOutput(),
result.message(),
result.success(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
@Schema(description = "각 테스트케이스에 대한 채점 결과")
public record JudgeResultResponse(

@Schema(description = "테스트케이스 번호", example = "1")
int seqId,
@Schema(description = "테스트케이스 아이디", example = "1")
Long testcaseId,

@Schema(description = "테스트케이스 통과 여부", example = "true")
boolean isPassed,
Expand All @@ -30,7 +30,7 @@ public record JudgeResultResponse(
) {
public static JudgeResultResponse from(TestcaseResultPayload payload) {
return new JudgeResultResponse(
payload.seqId(),
payload.testcaseId(),
payload.isPassed(),
payload.actualOutput(),
payload.executionTime(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public interface TestcaseJpaRepository extends JpaRepository<Testcase, Long> {
@EntityGraph(attributePaths = "problem")
List<Testcase> findAllByProblem(Problem problem);

@EntityGraph(attributePaths = "problem")
List<Testcase> findAllByProblem_Id(Long problemId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public List<Testcase> findAllByProblem(Problem problem) {
return testcaseJpaRepository.findAllByProblem(problem);
}

@Override
public List<Testcase> findAllByProblemId(Long problemId) {
return testcaseJpaRepository.findAllByProblem_Id(problemId);
}

@Override
public Testcase findByTestcase(Long testcaseId) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
package org.ezcode.codetest.presentation.submission;

import java.util.HashSet;
import java.util.List;
import java.util.Map;

import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;

import org.ezcode.codetest.application.submission.dto.request.review.CodeReviewRequest;
import org.ezcode.codetest.application.submission.dto.request.submission.CodeSubmitRequest;
import org.ezcode.codetest.application.submission.dto.response.review.CodeReviewResponse;
import org.ezcode.codetest.application.submission.dto.response.submission.GroupedSubmissionResponse;
import org.ezcode.codetest.application.submission.dto.response.submission.SubmitResponse;
import org.ezcode.codetest.application.submission.service.SubmissionService;
import org.ezcode.codetest.domain.user.model.entity.AuthUser;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -46,9 +40,7 @@ public class SubmissionController {
서버는 WebSocket(STOMP)을 통해 채점 결과를 실시간으로 전송합니다.

반환된 sessionKey를 사용해 다음 경로로 구독하세요.

• /user/queue/submission/{sessionKey}/init


• /user/queue/submission/{sessionKey}/case

• /user/queue/submission/{sessionKey}/final
Expand All @@ -63,16 +55,14 @@ public class SubmissionController {
@ApiResponse(responseCode = "400", description = "유효하지 않은 요청 데이터"),
@ApiResponse(responseCode = "409", description = "이미 해당 문제를 채점 중인 경우"),
})
public ResponseEntity<HashSet<String>> submitCodeStream(
public ResponseEntity<SubmitResponse> submitCodeStream(
@Parameter(description = "제출할 문제 ID", required = true) @PathVariable Long problemId,
@RequestBody @Valid CodeSubmitRequest request,
@AuthenticationPrincipal AuthUser authUser
) {
HashSet<String> set = new HashSet<>();
set.add(submissionService.enqueueCodeSubmission(problemId, request, authUser));
return ResponseEntity
.status(HttpStatus.OK)
.body(set);
.body(submissionService.enqueueCodeSubmission(problemId, request, authUser));
}

@Operation(
Expand Down
Loading