From 8d06d20a95993278268667826b0899096a392bec Mon Sep 17 00:00:00 2001 From: Byeonghyeon Lee Date: Sat, 24 Jul 2021 22:57:52 +0900 Subject: [PATCH 01/22] Enable running code before submission MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dispatcher에서 compiler error 등 발생하는 경우 아직 처리하지 않음 --- backend/judge/dispatcher.py | 85 +++++++++++++++++++ backend/judge/tasks.py | 7 +- backend/submission/urls.py | 3 +- backend/submission/views.py | 68 ++++++++++++++- backend/utils/constants.py | 1 + frontend/src/pages/oj/api.js | 12 +++ .../src/pages/oj/views/problem/Problem.vue | 39 ++++++++- 7 files changed, 210 insertions(+), 5 deletions(-) diff --git a/backend/judge/dispatcher.py b/backend/judge/dispatcher.py index 0a932f1d6..5372c8b07 100644 --- a/backend/judge/dispatcher.py +++ b/backend/judge/dispatcher.py @@ -31,6 +31,16 @@ def process_pending_task(): judge_task.send(**data) +def process_pending_task_run(): + if cache.llen(CacheKey.run_waiting_queue): + # Prevent loop introduction + from judge.tasks import coderun_task + tmp_data = cache.rpop(CacheKey.run_waiting_queue) + if tmp_data: + data = json.loads(tmp_data.decode("utf-8")) + coderun_task.send(**data) + + class ChooseJudgeServer: def __init__(self): self.server = None @@ -89,6 +99,79 @@ def compile_spj(self): return result["data"] +class CodeRunDispatcher(DispatcherBase): + def __init__(self, data): + super().__init__() + self.run_data = data + self.contest_id = data.get("contest_id") + problem_id = self.run_data["problem_id"] + + if self.contest_id: + self.problem = Problem.objects.select_related("contest").get(id=problem_id, contest_id=self.contest_id) + self.contest = self.problem.contest + else: + self.problem = Problem.objects.get(id=problem_id) + + def judge(self): + language = self.run_data["language"] + sub_config = list(filter(lambda item: language == item["name"], SysOptions.languages))[0] + spj_config = {} + if self.problem.spj_code: + for lang in SysOptions.spj_languages: + if lang["name"] == self.problem.spj_language: + spj_config = lang["spj"] + break + + if language in self.problem.template: + template = parse_problem_template(self.problem.template[language]) + code = f"{template['prepend']}\n{self.run_data.code}\n{template['append']}" + else: + code = self.run_data["code"] + + data = { + "language_config": sub_config["config"], + "src": code, + "max_cpu_time": self.problem.time_limit, + "max_memory": 1024 * 1024 * self.problem.memory_limit, + "test_case_id": None, + "test_case": self.problem.samples, + "output": True, + "spj_version": self.problem.spj_version, + "spj_config": spj_config.get("config"), + "spj_compile_config": spj_config.get("compile"), + "spj_src": self.problem.spj_code, + "io_mode": self.problem.io_mode + } + + if self.run_data["new_testcase"]: + for testcases in self.run_data["new_testcase"]: + data["test_case"].append({"input": testcases, "output": ""}) + + run_id = self.run_data["run_id"] + + with ChooseJudgeServer() as server: + if not server: + cache.lpush(CacheKey.running_waiting_queue, json.dumps(self.run_data)) + return + + resp = self._request(urljoin(server.service_url, "/judge"), data=data) + + if not resp: + pass + + if resp["err"]: # compile error. + pass + + output_data = resp["data"] + outputs = [] + for i in range(len(output_data)): + outputs.append(output_data[i]["output"]) + cache.hset("run", run_id, json.dumps(outputs)) + + # At this point, the judgment is over, try to process the remaining tasks in the task queue + process_pending_task_run() + + class JudgeDispatcher(DispatcherBase): def __init__(self, submission_id, problem_id): super().__init__() @@ -152,6 +235,8 @@ def judge(self): "spj_src": self.problem.spj_code, "io_mode": self.problem.io_mode } + data["output"] = True + data["test_case"] = [{"input": "30", "output":"31"}, {"input": "70", "output":"71"} ] with ChooseJudgeServer() as server: if not server: diff --git a/backend/judge/tasks.py b/backend/judge/tasks.py index 8a1794a19..667e3e721 100644 --- a/backend/judge/tasks.py +++ b/backend/judge/tasks.py @@ -2,7 +2,7 @@ from account.models import User from submission.models import Submission -from judge.dispatcher import JudgeDispatcher +from judge.dispatcher import JudgeDispatcher, CodeRunDispatcher from utils.shortcuts import DRAMATIQ_WORKER_ARGS @@ -12,3 +12,8 @@ def judge_task(submission_id, problem_id): if User.objects.get(id=uid).is_disabled: return JudgeDispatcher(submission_id, problem_id).judge() + + +@dramatiq.actor(**DRAMATIQ_WORKER_ARGS()) +def coderun_task(data): + CodeRunDispatcher(data).judge() diff --git a/backend/submission/urls.py b/backend/submission/urls.py index 78dd4c254..c77e40058 100644 --- a/backend/submission/urls.py +++ b/backend/submission/urls.py @@ -1,10 +1,11 @@ from django.conf.urls import url -from .views import SubmissionAPI, SubmissionListAPI, ContestSubmissionListAPI, SubmissionExistsAPI +from .views import SubmissionAPI, SubmissionListAPI, ContestSubmissionListAPI, SubmissionExistsAPI, CodeRunAPI urlpatterns = [ url(r"^submission/?$", SubmissionAPI.as_view(), name="submission_api"), url(r"^submissions/?$", SubmissionListAPI.as_view(), name="submission_list_api"), url(r"^submission_exists/?$", SubmissionExistsAPI.as_view(), name="submission_exists"), url(r"^contest_submissions/?$", ContestSubmissionListAPI.as_view(), name="contest_submission_list_api"), + url(r"^coderun/?$", CodeRunAPI.as_view(), name="coderun_api"), ] diff --git a/backend/submission/views.py b/backend/submission/views.py index 8fc48c346..43d36758b 100644 --- a/backend/submission/views.py +++ b/backend/submission/views.py @@ -1,11 +1,14 @@ import ipaddress +import logging +import uuid +import time from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from account.decorators import login_required, check_contest_permission from contest.models import ContestStatus, ContestRuleType -from judge.tasks import judge_task +from judge.tasks import judge_task, coderun_task from options.options import SysOptions # from judge.dispatcher import JudgeDispatcher from problem.models import Problem, ProblemRuleType @@ -18,6 +21,68 @@ ShareSubmissionSerializer) from .serializers import SubmissionSafeModelSerializer, SubmissionListSerializer +logger = logging.getLogger(__name__) + + +class CodeRunAPI(APIView): + def throttling(self, request): + user_bucket = TokenBucket(key=str(request.user.id), + redis_conn=cache, **SysOptions.throttling["user"]) + can_consume, wait = user_bucket.consume() + if not can_consume: + return "Please wait %d seconds" % (int(wait)) + + @login_required + def post(self, request): + data = request.data + hide_id = False + if data.get("contest_id"): + error = self.check_contest_permission(request) + if error: + return error + contest = self.contest + if not contest.problem_details_permission(request.user): + hide_id = True + + if data.get("captcha"): + if not Captcha(request).check(data["captcha"]): + return self.error("Invalid captcha") + error = self.throttling(request) + if error: + return self.error(error) + + try: + problem = Problem.objects.get(id=data["problem_id"], contest_id=data.get("contest_id"), visible=True) + except Problem.DoesNotExist: + return self.error("Problem not exist") + if data["language"] not in problem.languages: + return self.error(f"{data['language']} is now allowed in the problem") + + run_data = {} + run_data["language"] = data["language"] + run_data["code"] = data["code"] + run_data["contest_id"] = data.get("contest_id") + run_data["new_testcase"] = data.get("new_testcase") + run_data["problem_id"] = problem.id + run_id = uuid.uuid4().hex + run_data["run_id"] = run_id + + coderun_task.send(run_data) + return self.success(run_id) + + @login_required + def get(self, request): + run_id = request.GET.get("run_id") + time.sleep(2) + res = cache.hget("run", run_id) + res = res.decode("utf-8") + cache.hdel("run", run_id) + + tmp = cache.hget("run", run_id) + if tmp: + logger.warning('run data is not deleted.') + return self.success(res) + class SubmissionAPI(APIView): def throttling(self, request): @@ -73,7 +138,6 @@ def post(self, request): ip=request.session["ip"], contest_id=data.get("contest_id")) # use this for debug - # JudgeDispatcher(submission.id, problem.id).judge() judge_task.send(submission.id, problem.id) if hide_id: return self.success() diff --git a/backend/utils/constants.py b/backend/utils/constants.py index 1b6a82f9a..4bb4db38b 100644 --- a/backend/utils/constants.py +++ b/backend/utils/constants.py @@ -25,6 +25,7 @@ class CacheKey: waiting_queue = "waiting_queue" contest_rank_cache = "contest_rank_cache" website_config = "website_config" + run_waiting_queue = "run_waiting_queue" class Difficulty(Choices): diff --git a/frontend/src/pages/oj/api.js b/frontend/src/pages/oj/api.js index 2a7b4549f..f06cb9101 100644 --- a/frontend/src/pages/oj/api.js +++ b/frontend/src/pages/oj/api.js @@ -198,6 +198,18 @@ export default { data }) }, + runCode (data) { + return ajax('coderun', 'post', { + data + }) + }, + getRunResult (runID) { + return ajax('coderun', 'get', { + params: { + run_id: runID + } + }) + }, getSubmissionList (offset, limit, params) { params.limit = limit params.offset = offset diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index 9382977ff..5aa9ec262 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -98,7 +98,7 @@ - + Run @@ -374,6 +374,7 @@ export default { try { const res = await api.getSubmission(id) this.result = res.data.data + console.log(this.result) if (Object.keys(res.data.data.statistic_info).length !== 0) { this.submitting = false this.submitted = false @@ -454,6 +455,42 @@ export default { } else { await submitFunc(data, true) } + }, + checkRunState (runID) { + const checkStatus = () => { + api.getRunResult(runID).then(res => { + if (res.length !== 0) { + console.log(res) + clearTimeout(this.runRefresh) + } else { + this.runRefresh = setTimeout(checkStatus, 2000) + } + }) + } + this.runRefresh = setTimeout(checkStatus, 2000) + }, + runCode () { + if (this.code.trim() === '') { + this.$error(this.$i18n.t('m.Code_can_not_be_empty')) + return + } + const data = { + problem_id: this.problem.id, + language: this.language, + code: this.code, + contest_id: this.contestID, + new_testcase: null + } + var isNewTestcase = true // for test + if (isNewTestcase) { + data.new_testcase = ['10', '11', '12', '13'] + } + console.log('run this data') + console.log(data) + api.runCode(data).then(res => { + console.log(res.data.data) + this.checkRunState(res.data.data) + }) } }, computed: { From 20ef37b7704d2c8189ead6bfb929eb9696a826ac Mon Sep 17 00:00:00 2001 From: Byeonghyeon Lee Date: Sat, 24 Jul 2021 22:57:52 +0900 Subject: [PATCH 02/22] Handle the case of compile error and system error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ++ response.data의 err가 null 이면 정상적으로 작동한 것이고 compile error면 컴파일 에러가 발생한 것. 이경우 response.data.data에 error msg가 담겨져 있음 --- backend/judge/dispatcher.py | 23 +++++++++++++---------- backend/submission/views.py | 5 +++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/backend/judge/dispatcher.py b/backend/judge/dispatcher.py index 5372c8b07..b70c69134 100644 --- a/backend/judge/dispatcher.py +++ b/backend/judge/dispatcher.py @@ -157,16 +157,19 @@ def judge(self): resp = self._request(urljoin(server.service_url, "/judge"), data=data) if not resp: - pass - - if resp["err"]: # compile error. - pass - - output_data = resp["data"] - outputs = [] - for i in range(len(output_data)): - outputs.append(output_data[i]["output"]) - cache.hset("run", run_id, json.dumps(outputs)) + cache.hset("run", run_id, json.dumps("System Error")) + + elif resp["err"]: + cache.hset("run", run_id, json.dumps(resp)) + + else: + output_data = resp["data"] + outputs = {} + outputs["err"] = None + outputs["data"] = {} + for i in range(len(output_data)): + outputs["data"]["tc"+str(i+1)] = output_data[i]["output"] + cache.hset("run", run_id, json.dumps(outputs)) # At this point, the judgment is over, try to process the remaining tasks in the task queue process_pending_task_run() diff --git a/backend/submission/views.py b/backend/submission/views.py index 43d36758b..dafb3b05f 100644 --- a/backend/submission/views.py +++ b/backend/submission/views.py @@ -2,6 +2,7 @@ import logging import uuid import time +import json from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi @@ -73,9 +74,13 @@ def post(self, request): @login_required def get(self, request): run_id = request.GET.get("run_id") + if not cache.hexists("run", run_id): + return self.error("redis hash field error - such field doesn't exist") + time.sleep(2) res = cache.hget("run", run_id) res = res.decode("utf-8") + res = json.loads(res) cache.hdel("run", run_id) tmp = cache.hget("run", run_id) From b6f12da428afbdde7264f60f493de1c6cf66eba2 Mon Sep 17 00:00:00 2001 From: Byeonghyeon Lee Date: Sat, 24 Jul 2021 22:57:52 +0900 Subject: [PATCH 03/22] Handle the case of compile error and system error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ++ response.data의 err가 null 이면 정상적으로 작동한 것이고 compile error면 컴파일 에러가 발생한 것. 이경우 response.data.data에 error msg가 담겨져 있음 --- backend/judge/dispatcher.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/judge/dispatcher.py b/backend/judge/dispatcher.py index b70c69134..c702c49ac 100644 --- a/backend/judge/dispatcher.py +++ b/backend/judge/dispatcher.py @@ -238,8 +238,6 @@ def judge(self): "spj_src": self.problem.spj_code, "io_mode": self.problem.io_mode } - data["output"] = True - data["test_case"] = [{"input": "30", "output":"31"}, {"input": "70", "output":"71"} ] with ChooseJudgeServer() as server: if not server: From e82ae0e1d50d2c2495707554d1f0b7832e53212e Mon Sep 17 00:00:00 2001 From: Byeonghyeon Lee Date: Sat, 24 Jul 2021 22:57:52 +0900 Subject: [PATCH 04/22] Submission: Remove redundant code --- backend/submission/views.py | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/backend/submission/views.py b/backend/submission/views.py index dafb3b05f..2edf3da2f 100644 --- a/backend/submission/views.py +++ b/backend/submission/views.py @@ -25,30 +25,27 @@ logger = logging.getLogger(__name__) -class CodeRunAPI(APIView): - def throttling(self, request): - user_bucket = TokenBucket(key=str(request.user.id), - redis_conn=cache, **SysOptions.throttling["user"]) - can_consume, wait = user_bucket.consume() - if not can_consume: - return "Please wait %d seconds" % (int(wait)) +def throttling(request): + user_bucket = TokenBucket(key=str(request.user.id), + redis_conn=cache, **SysOptions.throttling["user"]) + can_consume, wait = user_bucket.consume() + if not can_consume: + return "Please wait %d seconds" % (int(wait)) +class CodeRunAPI(APIView): @login_required def post(self, request): data = request.data - hide_id = False + if data.get("contest_id"): error = self.check_contest_permission(request) if error: return error - contest = self.contest - if not contest.problem_details_permission(request.user): - hide_id = True if data.get("captcha"): if not Captcha(request).check(data["captcha"]): return self.error("Invalid captcha") - error = self.throttling(request) + error = throttling(request) if error: return self.error(error) @@ -90,13 +87,6 @@ def get(self, request): class SubmissionAPI(APIView): - def throttling(self, request): - user_bucket = TokenBucket(key=str(request.user.id), - redis_conn=cache, **SysOptions.throttling["user"]) - can_consume, wait = user_bucket.consume() - if not can_consume: - return "Please wait %d seconds" % (int(wait)) - @check_contest_permission(check_type="problems") def check_contest_permission(self, request): contest = self.contest @@ -125,7 +115,7 @@ def post(self, request): if data.get("captcha"): if not Captcha(request).check(data["captcha"]): return self.error("Invalid captcha") - error = self.throttling(request) + error = throttling(request) if error: return self.error(error) From f5d63f1144e4cfd8e5ae1efd18d733e073eeea91 Mon Sep 17 00:00:00 2001 From: Byeonghyeon Lee Date: Sat, 24 Jul 2021 22:57:52 +0900 Subject: [PATCH 05/22] Specify the type of error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ++ 코드를 run 해볼 때, 에러가 발생한 경우 에러 내용을 리턴 --- backend/judge/dispatcher.py | 43 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/backend/judge/dispatcher.py b/backend/judge/dispatcher.py index c702c49ac..4ec035cf2 100644 --- a/backend/judge/dispatcher.py +++ b/backend/judge/dispatcher.py @@ -1,6 +1,5 @@ import hashlib import json -import logging from urllib.parse import urljoin import requests @@ -17,8 +16,6 @@ from utils.cache import cache from utils.constants import CacheKey -logger = logging.getLogger(__name__) - # Continue to deal with problems in the queue def process_pending_task(): @@ -41,6 +38,15 @@ def process_pending_task_run(): coderun_task.send(**data) +class ErrorType: + err_type = { + "1": "CPU Time Exceeded", + "2": "Time Limit Exceeded", + "3": "Memory Limit Exceeded", + "4": "Runtime Error" + } + + class ChooseJudgeServer: def __init__(self): self.server = None @@ -156,19 +162,30 @@ def judge(self): resp = self._request(urljoin(server.service_url, "/judge"), data=data) - if not resp: - cache.hset("run", run_id, json.dumps("System Error")) + outputs = {} + if not resp: # System error + outputs["err"] = "System Error" + outputs["data"] = None + cache.hset("run", run_id, json.dumps(outputs)) - elif resp["err"]: + elif resp["err"]: # Compile error cache.hset("run", run_id, json.dumps(resp)) - else: - output_data = resp["data"] - outputs = {} - outputs["err"] = None - outputs["data"] = {} - for i in range(len(output_data)): - outputs["data"]["tc"+str(i+1)] = output_data[i]["output"] + else: # Other errors or normal operation + resp["data"].sort(key=lambda x: int(x["test_case"])) + error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"])) + + if self.problem.rule_type == ProblemRuleType.ACM or len(error_test_case) == len(resp["data"]): + err_code = str(error_test_case[0]["result"]) + outputs["err"] = ErrorType.err_type[err_code] + outputs["data"] = None + + elif not error_test_case: + output_data = resp["data"] + outputs["err"] = None + outputs["data"] = {} + for i in range(len(output_data)): + outputs["data"]["tc"+str(i+1)] = output_data[i]["output"] cache.hset("run", run_id, json.dumps(outputs)) # At this point, the judgment is over, try to process the remaining tasks in the task queue From 2d6dd268055ae33bef108eca7c38b1665bcde36d Mon Sep 17 00:00:00 2001 From: y-jiu Date: Sat, 24 Jul 2021 22:57:52 +0900 Subject: [PATCH 06/22] Update Problem.vue Added "Add Testcase" modal --- .../src/pages/oj/views/problem/Problem.vue | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index 5aa9ec262..898bdd95b 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -92,6 +92,29 @@ Refresh + + Add Testcase + + + Inputs + + Add + + +
+
+ + + + + + + + +
+
+
+
@@ -252,7 +275,11 @@ export default { tags: [], io_mode: { io_mode: 'Standard IO' } }, - + inputs: [ + { + name: '' + } + ], // CodeMirror code: '', language: 'C++', @@ -491,6 +518,12 @@ export default { console.log(res.data.data) this.checkRunState(res.data.data) }) + }, + add () { + this.inputs.push({ name: '' }) + }, + remove (index) { + this.inputs.splice(index, 1) } }, computed: { From ea6f7bf2300b45e0f032fe405abd901290e50708 Mon Sep 17 00:00:00 2001 From: Jimin Ha Date: Sat, 24 Jul 2021 22:57:52 +0900 Subject: [PATCH 07/22] Add feature add testcase & run --- .../src/pages/oj/views/problem/Problem.vue | 70 ++++++++++++------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index 898bdd95b..c199b2e00 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -93,25 +93,34 @@ - Add Testcase - + Add Testcase + - Inputs - + + Add Testcase + Add
-
+ - - - - - - + + -
+ + + + + + + + + + + +
@@ -265,6 +274,7 @@ export default { problem: { title: '', description: '', + samples: [], hint: '', my_status: '', template: {}, @@ -275,11 +285,7 @@ export default { tags: [], io_mode: { io_mode: 'Standard IO' } }, - inputs: [ - { - name: '' - } - ], + userTestcaseInputs: [], // CodeMirror code: '', language: 'C++', @@ -498,7 +504,7 @@ export default { }, runCode () { if (this.code.trim() === '') { - this.$error(this.$i18n.t('m.Code_can_not_be_empty')) + this.$error('Code can not be empty') return } const data = { @@ -508,9 +514,10 @@ export default { contest_id: this.contestID, new_testcase: null } - var isNewTestcase = true // for test + var isNewTestcase = true if (isNewTestcase) { - data.new_testcase = ['10', '11', '12', '13'] + var testcases = this.problem.samples.concat(this.userTestcaseInputs) + data.new_testcase = testcases.map(testcase => testcase.input) } console.log('run this data') console.log(data) @@ -519,11 +526,26 @@ export default { this.checkRunState(res.data.data) }) }, - add () { - this.inputs.push({ name: '' }) + handleUserTestcaseInput (bvModalEvt) { + bvModalEvt.preventDefault() + for (const userTestcaseInput of this.userTestcaseInputs) { + if (userTestcaseInput.input === '') { + this.$error('Testcase Should not be empty') + return + } + } + this.$nextTick(() => { + this.$bvModal.hide('modal-user-testcase') + }) + }, + isValidTestcaseInput () { + + }, + addTestcaseInput () { + this.userTestcaseInputs.push({ input: '' }) }, - remove (index) { - this.inputs.splice(index, 1) + removeTestcaseInput (index) { + this.userTestcaseInputs.splice(index, 1) } }, computed: { From ed8ec892d5fb1a9fe238683149466b348a2713ae Mon Sep 17 00:00:00 2001 From: Jimin Ha Date: Sat, 24 Jul 2021 22:57:52 +0900 Subject: [PATCH 08/22] Add Testcase modal EdgeCase resolved --- .../src/pages/oj/views/problem/Problem.vue | 56 +++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index c199b2e00..6744b870a 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -94,12 +94,12 @@ Add Testcase - + Add Testcase - + Add @@ -110,12 +110,12 @@ - + - + - + @@ -285,7 +285,8 @@ export default { tags: [], io_mode: { io_mode: 'Standard IO' } }, - userTestcaseInputs: [], + tempUserTestcases: [], + userTestcases: [], // CodeMirror code: '', language: 'C++', @@ -516,7 +517,7 @@ export default { } var isNewTestcase = true if (isNewTestcase) { - var testcases = this.problem.samples.concat(this.userTestcaseInputs) + var testcases = this.problem.samples.concat(this.userTestcases) data.new_testcase = testcases.map(testcase => testcase.input) } console.log('run this data') @@ -526,26 +527,37 @@ export default { this.checkRunState(res.data.data) }) }, - handleUserTestcaseInput (bvModalEvt) { - bvModalEvt.preventDefault() - for (const userTestcaseInput of this.userTestcaseInputs) { - if (userTestcaseInput.input === '') { + tempStoreUserTestcase () { + console.log('tempUserTestcase') + this.tempUserTestcases = JSON.parse(JSON.stringify(this.userTestcases)) + }, + beforeHideUserTestcaseModal (bvModalEvt) { + console.log('beforeHide') + if (bvModalEvt.trigger === 'ok') { + console.log('OK!') + } else { + this.userTestcases = JSON.parse(JSON.stringify(this.tempUserTestcases)) + console.log('restore!') + } + }, + saveUserTestcase (bvModalEvt) { + console.log('handleUserTestcase') + for (const userTestcase of this.userTestcases) { + if (userTestcase.input === '') { this.$error('Testcase Should not be empty') + bvModalEvt.preventDefault() return } } - this.$nextTick(() => { - this.$bvModal.hide('modal-user-testcase') - }) }, - isValidTestcaseInput () { - - }, - addTestcaseInput () { - this.userTestcaseInputs.push({ input: '' }) + addTestcase () { + if (this.userTestcases.length > 10) { + return + } + this.userTestcases.push({ input: '' }) }, - removeTestcaseInput (index) { - this.userTestcaseInputs.splice(index, 1) + removeTestcase (index) { + this.userTestcases.splice(index, 1) } }, computed: { From 1025b39c1eb2c9018aa934d044f95e44d5d3619e Mon Sep 17 00:00:00 2001 From: Jimin Ha Date: Sat, 24 Jul 2021 22:57:52 +0900 Subject: [PATCH 09/22] Add runResults for before api testing --- .../src/pages/oj/views/problem/Problem.vue | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index 6744b870a..0f941a3e8 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -206,6 +206,21 @@ :theme="theme" /> + + + + + + - - + @@ -303,7 +292,7 @@ export default { tempUserTestcases: [], userTestcases: [], // CodeMirror - runResults: [{ input: 'input1', output: 'output1' }, { input: 'input2', output: 'output2' }], + runResults: [], code: '', language: 'C++', theme: 'material', @@ -424,7 +413,6 @@ export default { try { const res = await api.getSubmission(id) this.result = res.data.data - console.log(this.result) if (Object.keys(res.data.data.statistic_info).length !== 0) { this.submitting = false this.submitted = false @@ -510,8 +498,7 @@ export default { const checkStatus = () => { api.getRunResult(runID).then(res => { if (res.length !== 0) { - console.log(res) - // this.runResults = res.data.data + this.runResults = res.data.data clearTimeout(this.runRefresh) } else { this.runRefresh = setTimeout(checkStatus, 2000) @@ -537,28 +524,20 @@ export default { var testcases = this.problem.samples.concat(this.userTestcases) data.new_testcase = testcases.map(testcase => testcase.input) } - console.log('run this data') - console.log(data) api.runCode(data).then(res => { - console.log(res.data.data) this.checkRunState(res.data.data) }) }, tempStoreUserTestcase () { - console.log('tempUserTestcase') this.tempUserTestcases = JSON.parse(JSON.stringify(this.userTestcases)) }, beforeHideUserTestcaseModal (bvModalEvt) { - console.log('beforeHide') if (bvModalEvt.trigger === 'ok') { - console.log('OK!') } else { this.userTestcases = JSON.parse(JSON.stringify(this.tempUserTestcases)) - console.log('restore!') } }, saveUserTestcase (bvModalEvt) { - console.log('handleUserTestcase') for (const userTestcase of this.userTestcases) { if (userTestcase.input === '') { this.$error('Testcase Should not be empty') @@ -790,11 +769,6 @@ export default { padding: 0; flex: 1 1 auto; } - #run-results { - margin: 0; - padding: 0px 15px; - flex: 0 1 auto; - } #io { margin: 0; padding: 0; @@ -830,6 +804,13 @@ export default { .io-content-cell { padding: 10px 15px; + + .sample-io { + min-height: 40px; + border-radius: 5px; + background: #24272D; + color: white; + } } } } From afb457092558ae6e9335027974e57da476c4ab53 Mon Sep 17 00:00:00 2001 From: Byeonghyeon Lee Date: Fri, 30 Jul 2021 14:34:07 +0900 Subject: [PATCH 11/22] Run: Handle error regarding WA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit +결과가 WA인 경우 redis error가 발생하는 이슈 해결 (cherry picked from commit bb39d04fa24a7f44bc9fdf6091c72b167a69d0b8) --- backend/judge/dispatcher.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/backend/judge/dispatcher.py b/backend/judge/dispatcher.py index 4ec035cf2..7d643c4f0 100644 --- a/backend/judge/dispatcher.py +++ b/backend/judge/dispatcher.py @@ -1,5 +1,6 @@ import hashlib import json +import logging from urllib.parse import urljoin import requests @@ -16,6 +17,8 @@ from utils.cache import cache from utils.constants import CacheKey +logger = logging.getLogger(__name__) + # Continue to deal with problems in the queue def process_pending_task(): @@ -173,19 +176,19 @@ def judge(self): else: # Other errors or normal operation resp["data"].sort(key=lambda x: int(x["test_case"])) - error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"])) - - if self.problem.rule_type == ProblemRuleType.ACM or len(error_test_case) == len(resp["data"]): - err_code = str(error_test_case[0]["result"]) - outputs["err"] = ErrorType.err_type[err_code] - outputs["data"] = None - - elif not error_test_case: - output_data = resp["data"] - outputs["err"] = None - outputs["data"] = {} - for i in range(len(output_data)): + output_data = resp["data"] + outputs["err"] = {} + outputs["data"] = {} + tc_num = len(output_data) + for i in range(tc_num): + if output_data[i]["result"] == -1 or output_data[i]["result"] == 0: + outputs["err"]["tc"+str(i+1)] = None outputs["data"]["tc"+str(i+1)] = output_data[i]["output"] + else: + err_code = str(output_data[i]["result"]) + outputs["err"]["tc"+str(i+1)] = ErrorType.err_type[err_code] + outputs["data"]["tc"+str(i+1)] = None + cache.hset("run", run_id, json.dumps(outputs)) # At this point, the judgment is over, try to process the remaining tasks in the task queue From 35b627b37fa0616ba59f40fc3d46b95fb61a6ce1 Mon Sep 17 00:00:00 2001 From: Byeonghyeon Lee Date: Sat, 31 Jul 2021 01:19:23 +0900 Subject: [PATCH 12/22] Run: Modify response format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit +input도 리턴을 하고, 리턴 형식도 객체 배열을 리턴하게 수정 (cherry picked from commit a9780765b4ee3e2982c93b5df66d9411bc6bdaef) --- backend/judge/dispatcher.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/backend/judge/dispatcher.py b/backend/judge/dispatcher.py index 7d643c4f0..4aa2b6c70 100644 --- a/backend/judge/dispatcher.py +++ b/backend/judge/dispatcher.py @@ -136,14 +136,14 @@ def judge(self): code = f"{template['prepend']}\n{self.run_data.code}\n{template['append']}" else: code = self.run_data["code"] - + data = { "language_config": sub_config["config"], "src": code, "max_cpu_time": self.problem.time_limit, "max_memory": 1024 * 1024 * self.problem.memory_limit, "test_case_id": None, - "test_case": self.problem.samples, + "test_case": [], "output": True, "spj_version": self.problem.spj_version, "spj_config": spj_config.get("config"), @@ -152,9 +152,8 @@ def judge(self): "io_mode": self.problem.io_mode } - if self.run_data["new_testcase"]: - for testcases in self.run_data["new_testcase"]: - data["test_case"].append({"input": testcases, "output": ""}) + for testcases in self.run_data["new_testcase"]: + data["test_case"].append({"input": testcases, "output": ""}) run_id = self.run_data["run_id"] @@ -165,7 +164,8 @@ def judge(self): resp = self._request(urljoin(server.service_url, "/judge"), data=data) - outputs = {} + outputs = [] + if not resp: # System error outputs["err"] = "System Error" outputs["data"] = None @@ -177,17 +177,21 @@ def judge(self): else: # Other errors or normal operation resp["data"].sort(key=lambda x: int(x["test_case"])) output_data = resp["data"] - outputs["err"] = {} - outputs["data"] = {} tc_num = len(output_data) for i in range(tc_num): + output_ele = {} + output_ele["output"] = {} + output_ele["input"] = data["test_case"][i]["input"] if output_data[i]["result"] == -1 or output_data[i]["result"] == 0: - outputs["err"]["tc"+str(i+1)] = None - outputs["data"]["tc"+str(i+1)] = output_data[i]["output"] + output_ele["output"]["err"] = None + output_ele["output"]["data"] = output_data[i]["output"] + else: err_code = str(output_data[i]["result"]) - outputs["err"]["tc"+str(i+1)] = ErrorType.err_type[err_code] - outputs["data"]["tc"+str(i+1)] = None + output_ele["output"]["err"] = ErrorType.err_type[err_code] + output_ele["output"]["data"] = None + + outputs.append(output_ele) cache.hset("run", run_id, json.dumps(outputs)) From 0264deb55b70579930706719c38abcf1370088aa Mon Sep 17 00:00:00 2001 From: Jimin Ha Date: Sat, 31 Jul 2021 13:40:41 +0900 Subject: [PATCH 13/22] Run Results design revise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- API response 구조 변경에 따라 runresults 출력하는 방법과 디자인 바꿈 (cherry picked from commit 9d61a5cc22567aa3f9c69c64e2b2fb9a1b7f786a) --- .../src/pages/oj/views/problem/Problem.vue | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index 90f3b3bdb..275df9085 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -208,18 +208,18 @@ - Number - Input - Output + # + Input + Output - +
{{ index }}
- +
{{ input }}
- +
{{ output }}
@@ -494,11 +494,29 @@ export default { await submitFunc(data, true) } }, + saveRunResult (data) { + console.log('1') + console.log(data) + if (data.data.err === 'CompileError') { + this.runResults = [{ input: 'Compile Error\n' + data.data.data, output: '' }] + } else { + this.runResults = [] + for (const runResult of data.data) { + console.log('2') + console.log(runResult) + if (runResult.output.err) { + this.runResults.push({ input: runResult.input, output: runResult.output.err }) + } else { + this.runResults.push({ input: runResult.input, output: runResult.output.data }) + } + } + } + }, checkRunState (runID) { const checkStatus = () => { api.getRunResult(runID).then(res => { - if (res.length !== 0) { - this.runResults = res.data.data + if (res.length !== 0 && res.error !== null) { + this.saveRunResult(res.data) clearTimeout(this.runRefresh) } else { this.runRefresh = setTimeout(checkStatus, 2000) From 173c665c1dd289e56ae6fee0c3bdb2dc1f966cca Mon Sep 17 00:00:00 2001 From: Jimin Ha Date: Sat, 31 Jul 2021 14:44:44 +0900 Subject: [PATCH 14/22] runresult compile error design revise (cherry picked from commit d51b0c400fb4b21a7410f8f40b504b3aa76920e1) --- frontend/src/pages/oj/views/problem/Problem.vue | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index 275df9085..8dd4065cd 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -212,15 +212,15 @@ Input Output
- - -
{{ index }}
+ + +
{{ index + 1 }}
- -
{{ input }}
+ +
{{ runResult.input }}
- -
{{ output }}
+ +
{{ runResult.output }}
@@ -498,7 +498,7 @@ export default { console.log('1') console.log(data) if (data.data.err === 'CompileError') { - this.runResults = [{ input: 'Compile Error\n' + data.data.data, output: '' }] + this.runResults = [{ input: 'Error Message: \n' + data.data.data, output: '' }] } else { this.runResults = [] for (const runResult of data.data) { From 940bae0d90c41f83f0e67f4dafc317b20f83f92f Mon Sep 17 00:00:00 2001 From: Jimin Ha Date: Sat, 31 Jul 2021 15:00:05 +0900 Subject: [PATCH 15/22] RunResults code refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- await, async 비동기 처리 (cherry picked from commit 6bbb5734abcbc0d280b84a8d30a6e21db8be2349) --- .../src/pages/oj/views/problem/Problem.vue | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index 8dd4065cd..c90e11c07 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -495,15 +495,11 @@ export default { } }, saveRunResult (data) { - console.log('1') - console.log(data) if (data.data.err === 'CompileError') { this.runResults = [{ input: 'Error Message: \n' + data.data.data, output: '' }] } else { this.runResults = [] for (const runResult of data.data) { - console.log('2') - console.log(runResult) if (runResult.output.err) { this.runResults.push({ input: runResult.input, output: runResult.output.err }) } else { @@ -513,19 +509,18 @@ export default { } }, checkRunState (runID) { - const checkStatus = () => { - api.getRunResult(runID).then(res => { - if (res.length !== 0 && res.error !== null) { - this.saveRunResult(res.data) - clearTimeout(this.runRefresh) - } else { - this.runRefresh = setTimeout(checkStatus, 2000) - } - }) + const checkStatus = async () => { + const res = await api.getRunResult(runID) + if (res.length !== 0 && res.error !== null) { + this.saveRunResult(res.data) + clearTimeout(this.runRefresh) + } else { + this.runRefresh = setTimeout(checkStatus, 2000) + } } this.runRefresh = setTimeout(checkStatus, 2000) }, - runCode () { + async runCode () { if (this.code.trim() === '') { this.$error('Code can not be empty') return @@ -537,14 +532,10 @@ export default { contest_id: this.contestID, new_testcase: null } - var isNewTestcase = true - if (isNewTestcase) { - var testcases = this.problem.samples.concat(this.userTestcases) - data.new_testcase = testcases.map(testcase => testcase.input) - } - api.runCode(data).then(res => { - this.checkRunState(res.data.data) - }) + const testcases = this.problem.samples.concat(this.userTestcases) + data.new_testcase = testcases.map(testcase => testcase.input) + const res = await api.runCode(data) + this.checkRunState(res.data.data) }, tempStoreUserTestcase () { this.tempUserTestcases = JSON.parse(JSON.stringify(this.userTestcases)) From 37a43c2137629aaee0214e892290c16461ec500b Mon Sep 17 00:00:00 2001 From: Byeonghyeon Lee Date: Sat, 31 Jul 2021 04:44:24 +0900 Subject: [PATCH 16/22] Modify code to follow the Python code style --- backend/judge/dispatcher.py | 26 +++++++++++++------------- backend/submission/views.py | 17 ++++++++--------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/backend/judge/dispatcher.py b/backend/judge/dispatcher.py index 4aa2b6c70..9ab1b2f31 100644 --- a/backend/judge/dispatcher.py +++ b/backend/judge/dispatcher.py @@ -32,7 +32,7 @@ def process_pending_task(): def process_pending_task_run(): - if cache.llen(CacheKey.run_waiting_queue): + if cache.llen(CacheKey.run_waiting_queue): # Prevent loop introduction from judge.tasks import coderun_task tmp_data = cache.rpop(CacheKey.run_waiting_queue) @@ -136,7 +136,7 @@ def judge(self): code = f"{template['prepend']}\n{self.run_data.code}\n{template['append']}" else: code = self.run_data["code"] - + data = { "language_config": sub_config["config"], "src": code, @@ -155,26 +155,26 @@ def judge(self): for testcases in self.run_data["new_testcase"]: data["test_case"].append({"input": testcases, "output": ""}) - run_id = self.run_data["run_id"] + run_id = self.run_data["run_id"] with ChooseJudgeServer() as server: if not server: - cache.lpush(CacheKey.running_waiting_queue, json.dumps(self.run_data)) + cache.lpush(CacheKey.running_waiting_queue, json.dumps(self.run_data)) return - + resp = self._request(urljoin(server.service_url, "/judge"), data=data) outputs = [] - - if not resp: # System error + + if not resp: # System error outputs["err"] = "System Error" outputs["data"] = None cache.hset("run", run_id, json.dumps(outputs)) - elif resp["err"]: # Compile error + elif resp["err"]: # Compile error cache.hset("run", run_id, json.dumps(resp)) - - else: # Other errors or normal operation + + else: # Other errors or normal operation resp["data"].sort(key=lambda x: int(x["test_case"])) output_data = resp["data"] tc_num = len(output_data) @@ -185,12 +185,12 @@ def judge(self): if output_data[i]["result"] == -1 or output_data[i]["result"] == 0: output_ele["output"]["err"] = None output_ele["output"]["data"] = output_data[i]["output"] - + else: err_code = str(output_data[i]["result"]) - output_ele["output"]["err"] = ErrorType.err_type[err_code] + output_ele["output"]["err"] = ErrorType.err_type[err_code] output_ele["output"]["data"] = None - + outputs.append(output_ele) cache.hset("run", run_id, json.dumps(outputs)) diff --git a/backend/submission/views.py b/backend/submission/views.py index 2edf3da2f..8e4170382 100644 --- a/backend/submission/views.py +++ b/backend/submission/views.py @@ -1,7 +1,6 @@ import ipaddress import logging import uuid -import time import json from drf_yasg.utils import swagger_auto_schema @@ -27,16 +26,17 @@ def throttling(request): user_bucket = TokenBucket(key=str(request.user.id), - redis_conn=cache, **SysOptions.throttling["user"]) + redis_conn=cache, **SysOptions.throttling["user"]) can_consume, wait = user_bucket.consume() if not can_consume: return "Please wait %d seconds" % (int(wait)) + class CodeRunAPI(APIView): @login_required def post(self, request): data = request.data - + if data.get("contest_id"): error = self.check_contest_permission(request) if error: @@ -58,13 +58,13 @@ def post(self, request): run_data = {} run_data["language"] = data["language"] - run_data["code"] = data["code"] + run_data["code"] = data["code"] run_data["contest_id"] = data.get("contest_id") run_data["new_testcase"] = data.get("new_testcase") run_data["problem_id"] = problem.id run_id = uuid.uuid4().hex run_data["run_id"] = run_id - + coderun_task.send(run_data) return self.success(run_id) @@ -73,16 +73,15 @@ def get(self, request): run_id = request.GET.get("run_id") if not cache.hexists("run", run_id): return self.error("redis hash field error - such field doesn't exist") - - time.sleep(2) + res = cache.hget("run", run_id) res = res.decode("utf-8") res = json.loads(res) cache.hdel("run", run_id) - tmp = cache.hget("run", run_id) + tmp = cache.hget("run", run_id) if tmp: - logger.warning('run data is not deleted.') + logger.warning("run data is not deleted.") return self.success(res) From 0d291ee8800e0c24656c7eaa624039e06b075eba Mon Sep 17 00:00:00 2001 From: cyw320712 Date: Wed, 8 Sep 2021 23:31:55 +0900 Subject: [PATCH 17/22] Apply comments from dotoleeoak --- backend/judge/dispatcher.py | 265 +++++++++++++++--------------------- backend/judge/tasks.py | 10 +- backend/submission/views.py | 13 +- 3 files changed, 121 insertions(+), 167 deletions(-) diff --git a/backend/judge/dispatcher.py b/backend/judge/dispatcher.py index 9ab1b2f31..06b6c90d5 100644 --- a/backend/judge/dispatcher.py +++ b/backend/judge/dispatcher.py @@ -41,15 +41,6 @@ def process_pending_task_run(): coderun_task.send(**data) -class ErrorType: - err_type = { - "1": "CPU Time Exceeded", - "2": "Time Limit Exceeded", - "3": "Memory Limit Exceeded", - "4": "Runtime Error" - } - - class ChooseJudgeServer: def __init__(self): self.server = None @@ -108,104 +99,20 @@ def compile_spj(self): return result["data"] -class CodeRunDispatcher(DispatcherBase): +class JudgeDispatcher(DispatcherBase): def __init__(self, data): super().__init__() - self.run_data = data - self.contest_id = data.get("contest_id") - problem_id = self.run_data["problem_id"] - - if self.contest_id: - self.problem = Problem.objects.select_related("contest").get(id=problem_id, contest_id=self.contest_id) - self.contest = self.problem.contest - else: - self.problem = Problem.objects.get(id=problem_id) - - def judge(self): - language = self.run_data["language"] - sub_config = list(filter(lambda item: language == item["name"], SysOptions.languages))[0] - spj_config = {} - if self.problem.spj_code: - for lang in SysOptions.spj_languages: - if lang["name"] == self.problem.spj_language: - spj_config = lang["spj"] - break - - if language in self.problem.template: - template = parse_problem_template(self.problem.template[language]) - code = f"{template['prepend']}\n{self.run_data.code}\n{template['append']}" + if data["submission_id"]: + self.submission = Submission.objects.get(id=data["submission_id"]) + self.contest_id = self.submission.contest_id + self.last_result = self.submission.result if self.submission.info else None + self.language = self.submission.language else: - code = self.run_data["code"] - - data = { - "language_config": sub_config["config"], - "src": code, - "max_cpu_time": self.problem.time_limit, - "max_memory": 1024 * 1024 * self.problem.memory_limit, - "test_case_id": None, - "test_case": [], - "output": True, - "spj_version": self.problem.spj_version, - "spj_config": spj_config.get("config"), - "spj_compile_config": spj_config.get("compile"), - "spj_src": self.problem.spj_code, - "io_mode": self.problem.io_mode - } - - for testcases in self.run_data["new_testcase"]: - data["test_case"].append({"input": testcases, "output": ""}) - - run_id = self.run_data["run_id"] - - with ChooseJudgeServer() as server: - if not server: - cache.lpush(CacheKey.running_waiting_queue, json.dumps(self.run_data)) - return - - resp = self._request(urljoin(server.service_url, "/judge"), data=data) - - outputs = [] - - if not resp: # System error - outputs["err"] = "System Error" - outputs["data"] = None - cache.hset("run", run_id, json.dumps(outputs)) - - elif resp["err"]: # Compile error - cache.hset("run", run_id, json.dumps(resp)) - - else: # Other errors or normal operation - resp["data"].sort(key=lambda x: int(x["test_case"])) - output_data = resp["data"] - tc_num = len(output_data) - for i in range(tc_num): - output_ele = {} - output_ele["output"] = {} - output_ele["input"] = data["test_case"][i]["input"] - if output_data[i]["result"] == -1 or output_data[i]["result"] == 0: - output_ele["output"]["err"] = None - output_ele["output"]["data"] = output_data[i]["output"] - - else: - err_code = str(output_data[i]["result"]) - output_ele["output"]["err"] = ErrorType.err_type[err_code] - output_ele["output"]["data"] = None - - outputs.append(output_ele) - - cache.hset("run", run_id, json.dumps(outputs)) - - # At this point, the judgment is over, try to process the remaining tasks in the task queue - process_pending_task_run() - - -class JudgeDispatcher(DispatcherBase): - def __init__(self, submission_id, problem_id): - super().__init__() - self.submission = Submission.objects.get(id=submission_id) - self.contest_id = self.submission.contest_id - self.last_result = self.submission.result if self.submission.info else None + self.run_data = data + self.contest_id = data.get("contest_id") + self.language = self.run_data["language"] + problem_id = data["problem_id"] if self.contest_id: self.problem = Problem.objects.select_related("contest").get(id=problem_id, contest_id=self.contest_id) self.contest = self.problem.contest @@ -234,8 +141,7 @@ def _compute_statistic_info(self, resp_data): self.submission.statistic_info["score"] = score def judge(self): - language = self.submission.language - sub_config = list(filter(lambda item: language == item["name"], SysOptions.languages))[0] + sub_config = list(filter(lambda item: self.language == item["name"], SysOptions.languages))[0] spj_config = {} if self.problem.spj_code: for lang in SysOptions.spj_languages: @@ -243,11 +149,13 @@ def judge(self): spj_config = lang["spj"] break - if language in self.problem.template: - template = parse_problem_template(self.problem.template[language]) + if self.language in self.problem.template: + template = parse_problem_template(self.problem.template[self.language]) code = f"{template['prepend']}\n{self.submission.code}\n{template['append']}" - else: + elif self.data.submission_id: code = self.submission.code + else: + code = self.run_data["code"] data = { "language_config": sub_config["config"], @@ -263,57 +171,108 @@ def judge(self): "io_mode": self.problem.io_mode } - with ChooseJudgeServer() as server: - if not server: - data = {"submission_id": self.submission.id, "problem_id": self.problem.id} - cache.lpush(CacheKey.waiting_queue, json.dumps(data)) - return - Submission.objects.filter(id=self.submission.id).update(result=JudgeStatus.JUDGING) - resp = self._request(urljoin(server.service_url, "/judge"), data=data) + if not self.data.submission_id: + data["test_case_id"] = None + data["output"] = True + data["test_case"] = [] + for testcases in self.run_data["new_testcase"]: + data["test_case"].append({"input": testcases, "output": ""}) + run_id = self.run_data["run_id"] + + if self.data.submission_id: + with ChooseJudgeServer() as server: + if not server: + data = {"submission_id": self.submission.id, "problem_id": self.problem.id} + cache.lpush(CacheKey.waiting_queue, json.dumps(data)) + return + Submission.objects.filter(id=self.submission.id).update(result=JudgeStatus.JUDGING) + resp = self._request(urljoin(server.service_url, "/judge"), data=data) - if not resp: - Submission.objects.filter(id=self.submission.id).update(result=JudgeStatus.SYSTEM_ERROR) - return + if not resp: + Submission.objects.filter(id=self.submission.id).update(result=JudgeStatus.SYSTEM_ERROR) + return - if resp["err"]: - self.submission.result = JudgeStatus.COMPILE_ERROR - self.submission.statistic_info["err_info"] = resp["data"] - self.submission.statistic_info["score"] = 0 - else: - resp["data"].sort(key=lambda x: int(x["test_case"])) - self.submission.info = resp - self._compute_statistic_info(resp["data"]) - error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"])) - # In ACM mode, if multiple test points are all correct, then AC, - # otherwise, take the status of the first wrong test point - # In OI mode, if multiple test points are all correct, AC is used, - # if all test points are wrong, the first test point state is taken as the first error, - # otherwise it is partially correct - if not error_test_case: - self.submission.result = JudgeStatus.ACCEPTED - elif self.problem.rule_type == ProblemRuleType.ACM or len(error_test_case) == len(resp["data"]): - self.submission.result = error_test_case[0]["result"] + if resp["err"]: + self.submission.result = JudgeStatus.COMPILE_ERROR + self.submission.statistic_info["err_info"] = resp["data"] + self.submission.statistic_info["score"] = 0 + else: + resp["data"].sort(key=lambda x: int(x["test_case"])) + self.submission.info = resp + self._compute_statistic_info(resp["data"]) + error_test_case = list(filter(lambda case: case["result"] != 0, resp["data"])) + # In ACM mode, if multiple test points are all correct, then AC, + # otherwise, take the status of the first wrong test point + # In OI mode, if multiple test points are all correct, AC is used, + # if all test points are wrong, the first test point state is taken as the first error, + # otherwise it is partially correct + if not error_test_case: + self.submission.result = JudgeStatus.ACCEPTED + elif self.problem.rule_type == ProblemRuleType.ACM or len(error_test_case) == len(resp["data"]): + self.submission.result = error_test_case[0]["result"] + else: + self.submission.result = JudgeStatus.PARTIALLY_ACCEPTED + self.submission.save() + + if self.contest_id: + if self.contest.status != ContestStatus.CONTEST_UNDERWAY or \ + User.objects.get(id=self.submission.user_id).is_contest_admin(self.contest): + logger.info( + "Contest debug mode, id: " + str(self.contest_id) + ", submission id: " + self.submission.id) + return + with transaction.atomic(): + self.update_contest_problem_status() + self.update_contest_rank() else: - self.submission.result = JudgeStatus.PARTIALLY_ACCEPTED - self.submission.save() + if self.last_result: + self.update_problem_status_rejudge() + else: + self.update_problem_status() - if self.contest_id: - if self.contest.status != ContestStatus.CONTEST_UNDERWAY or \ - User.objects.get(id=self.submission.user_id).is_contest_admin(self.contest): - logger.info( - "Contest debug mode, id: " + str(self.contest_id) + ", submission id: " + self.submission.id) - return - with transaction.atomic(): - self.update_contest_problem_status() - self.update_contest_rank() + # At this point, the judgment is over, try to process the remaining tasks in the task queue + process_pending_task() else: - if self.last_result: - self.update_problem_status_rejudge() - else: - self.update_problem_status() + with ChooseJudgeServer() as server: + if not server: + cache.lpush(CacheKey.running_waiting_queue, json.dumps(self.run_data)) + return + + resp = self._request(urljoin(server.service_url, "/judge"), data=data) + + outputs = [] + + if not resp: # System error + outputs["err"] = "System Error" + outputs["data"] = None + cache.hset("run", run_id, json.dumps(outputs)) + + elif resp["err"]: # Compile error + cache.hset("run", run_id, json.dumps(resp)) + + else: # Other errors or normal operation + resp["data"].sort(key=lambda x: int(x["test_case"])) + output_data = resp["data"] + tc_num = len(output_data) + for i in range(tc_num): + output_ele = {} + output_ele["output"] = {} + output_ele["input"] = data["test_case"][i]["input"] + if output_data[i]["result"] in (-1, 0): + output_ele["output"]["err"] = None + output_ele["output"]["data"] = output_data[i]["output"] + + else: + err_code = output_data[i]["result"] + err_type = ["CPU Time Exceeded", "Time Limit Exceeded", "Memory Limit Exceeded", "Runtime Error"] + output_ele["output"]["err"] = err_type[err_code] + output_ele["output"]["data"] = None + + outputs.append(output_ele) + + cache.hset("run", run_id, json.dumps(outputs)) - # At this point, the judgment is over, try to process the remaining tasks in the task queue - process_pending_task() + # At this point, the judgment is over, try to process the remaining tasks in the task queue + process_pending_task_run() def update_problem_status_rejudge(self): result = str(self.submission.result) diff --git a/backend/judge/tasks.py b/backend/judge/tasks.py index 667e3e721..0638c39d6 100644 --- a/backend/judge/tasks.py +++ b/backend/judge/tasks.py @@ -2,18 +2,18 @@ from account.models import User from submission.models import Submission -from judge.dispatcher import JudgeDispatcher, CodeRunDispatcher +from judge.dispatcher import JudgeDispatcher from utils.shortcuts import DRAMATIQ_WORKER_ARGS @dramatiq.actor(**DRAMATIQ_WORKER_ARGS()) -def judge_task(submission_id, problem_id): - uid = Submission.objects.get(id=submission_id).user_id +def judge_task(data): + uid = Submission.objects.get(id=data["submission_id"]).user_id if User.objects.get(id=uid).is_disabled: return - JudgeDispatcher(submission_id, problem_id).judge() + JudgeDispatcher(data).judge() @dramatiq.actor(**DRAMATIQ_WORKER_ARGS()) def coderun_task(data): - CodeRunDispatcher(data).judge() + JudgeDispatcher(data).judge() diff --git a/backend/submission/views.py b/backend/submission/views.py index 8e4170382..19c51a404 100644 --- a/backend/submission/views.py +++ b/backend/submission/views.py @@ -1,6 +1,4 @@ import ipaddress -import logging -import uuid import json from drf_yasg.utils import swagger_auto_schema @@ -14,6 +12,7 @@ from problem.models import Problem, ProblemRuleType from utils.api import APIView, validate_serializer from utils.cache import cache +from utils.shortcuts import rand_str from utils.captcha import Captcha from utils.throttling import TokenBucket from .models import Submission @@ -21,8 +20,6 @@ ShareSubmissionSerializer) from .serializers import SubmissionSafeModelSerializer, SubmissionListSerializer -logger = logging.getLogger(__name__) - def throttling(request): user_bucket = TokenBucket(key=str(request.user.id), @@ -62,7 +59,7 @@ def post(self, request): run_data["contest_id"] = data.get("contest_id") run_data["new_testcase"] = data.get("new_testcase") run_data["problem_id"] = problem.id - run_id = uuid.uuid4().hex + run_id = rand_str() run_data["run_id"] = run_id coderun_task.send(run_data) @@ -79,9 +76,6 @@ def get(self, request): res = json.loads(res) cache.hdel("run", run_id) - tmp = cache.hget("run", run_id) - if tmp: - logger.warning("run data is not deleted.") return self.success(res) @@ -132,7 +126,8 @@ def post(self, request): ip=request.session["ip"], contest_id=data.get("contest_id")) # use this for debug - judge_task.send(submission.id, problem.id) + temp_data = {"submission_id": submission.id, "problem_id": problem.id} + judge_task.send(temp_data) if hide_id: return self.success() else: From 8fbf3cb92346cf4880e59a9c90e69e7b187931af Mon Sep 17 00:00:00 2001 From: HAKSAN Date: Thu, 9 Sep 2021 13:14:22 +0900 Subject: [PATCH 18/22] Fix minor issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit redis error 미해결 --- backend/judge/dispatcher.py | 21 +++++++++---------- backend/submission/views.py | 2 +- .../src/pages/oj/views/problem/Problem.vue | 4 ++-- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/backend/judge/dispatcher.py b/backend/judge/dispatcher.py index 06b6c90d5..b76f84eeb 100644 --- a/backend/judge/dispatcher.py +++ b/backend/judge/dispatcher.py @@ -102,8 +102,10 @@ def compile_spj(self): class JudgeDispatcher(DispatcherBase): def __init__(self, data): super().__init__() - if data["submission_id"]: - self.submission = Submission.objects.get(id=data["submission_id"]) + self.submission_id = data.get("submission_id") + problem_id = data.get("problem_id") + if self.submission_id: + self.submission = Submission.objects.get(id=self.submission_id) self.contest_id = self.submission.contest_id self.last_result = self.submission.result if self.submission.info else None self.language = self.submission.language @@ -112,7 +114,6 @@ def __init__(self, data): self.contest_id = data.get("contest_id") self.language = self.run_data["language"] - problem_id = data["problem_id"] if self.contest_id: self.problem = Problem.objects.select_related("contest").get(id=problem_id, contest_id=self.contest_id) self.contest = self.problem.contest @@ -152,7 +153,7 @@ def judge(self): if self.language in self.problem.template: template = parse_problem_template(self.problem.template[self.language]) code = f"{template['prepend']}\n{self.submission.code}\n{template['append']}" - elif self.data.submission_id: + elif self.submission_id: code = self.submission.code else: code = self.run_data["code"] @@ -171,7 +172,7 @@ def judge(self): "io_mode": self.problem.io_mode } - if not self.data.submission_id: + if not self.submission_id: data["test_case_id"] = None data["output"] = True data["test_case"] = [] @@ -179,7 +180,7 @@ def judge(self): data["test_case"].append({"input": testcases, "output": ""}) run_id = self.run_data["run_id"] - if self.data.submission_id: + if self.submission_id: with ChooseJudgeServer() as server: if not server: data = {"submission_id": self.submission.id, "problem_id": self.problem.id} @@ -234,16 +235,14 @@ def judge(self): else: with ChooseJudgeServer() as server: if not server: - cache.lpush(CacheKey.running_waiting_queue, json.dumps(self.run_data)) + cache.lpush(CacheKey.run_waiting_queue, json.dumps(self.run_data)) return - resp = self._request(urljoin(server.service_url, "/judge"), data=data) outputs = [] if not resp: # System error - outputs["err"] = "System Error" - outputs["data"] = None + outputs.append({"err": "System Error", "data": "System Error"}) cache.hset("run", run_id, json.dumps(outputs)) elif resp["err"]: # Compile error @@ -264,7 +263,7 @@ def judge(self): else: err_code = output_data[i]["result"] err_type = ["CPU Time Exceeded", "Time Limit Exceeded", "Memory Limit Exceeded", "Runtime Error"] - output_ele["output"]["err"] = err_type[err_code] + output_ele["output"]["err"] = err_type[err_code-1] output_ele["output"]["data"] = None outputs.append(output_ele) diff --git a/backend/submission/views.py b/backend/submission/views.py index 19c51a404..6254d01b7 100644 --- a/backend/submission/views.py +++ b/backend/submission/views.py @@ -61,7 +61,7 @@ def post(self, request): run_data["problem_id"] = problem.id run_id = rand_str() run_data["run_id"] = run_id - + cache.hset("run", run_id, json.dumps("Judging")) coderun_task.send(run_data) return self.success(run_id) diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index c90e11c07..ea0e79378 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -495,7 +495,7 @@ export default { } }, saveRunResult (data) { - if (data.data.err === 'CompileError') { + if (data.data.err != null) { this.runResults = [{ input: 'Error Message: \n' + data.data.data, output: '' }] } else { this.runResults = [] @@ -511,7 +511,7 @@ export default { checkRunState (runID) { const checkStatus = async () => { const res = await api.getRunResult(runID) - if (res.length !== 0 && res.error !== null) { + if (res.length !== 0 && res.error !== null && res.data.data !== 'Judging') { this.saveRunResult(res.data) clearTimeout(this.runRefresh) } else { From adf50fe7721a61d8b0df51b80de0cff3637c74a1 Mon Sep 17 00:00:00 2001 From: YONGWOO CHOI <42880886+cyw320712@users.noreply.github.com> Date: Thu, 9 Sep 2021 15:48:58 +0900 Subject: [PATCH 19/22] Update dispatcher.py --- backend/judge/dispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/judge/dispatcher.py b/backend/judge/dispatcher.py index b76f84eeb..b22e6eea4 100644 --- a/backend/judge/dispatcher.py +++ b/backend/judge/dispatcher.py @@ -262,8 +262,8 @@ def judge(self): else: err_code = output_data[i]["result"] - err_type = ["CPU Time Exceeded", "Time Limit Exceeded", "Memory Limit Exceeded", "Runtime Error"] - output_ele["output"]["err"] = err_type[err_code-1] + err_type = ["1": "CPU Time Exceeded", "2": "Time Limit Exceeded", "3": "Memory Limit Exceeded", "4": "Runtime Error"] + output_ele["output"]["err"] = err_type[err_code] output_ele["output"]["data"] = None outputs.append(output_ele) From 0162869577f3403f809e3cd4549c5e80efcf0f36 Mon Sep 17 00:00:00 2001 From: HAKSAN Date: Fri, 10 Sep 2021 23:57:10 +0900 Subject: [PATCH 20/22] Fix minor issues Check JudgeStatus at frontend Rename variables --- backend/judge/dispatcher.py | 55 ++++++++----------- backend/submission/views.py | 6 +- .../src/pages/oj/views/problem/Problem.vue | 2 +- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/backend/judge/dispatcher.py b/backend/judge/dispatcher.py index b22e6eea4..b59a6e081 100644 --- a/backend/judge/dispatcher.py +++ b/backend/judge/dispatcher.py @@ -103,7 +103,6 @@ class JudgeDispatcher(DispatcherBase): def __init__(self, data): super().__init__() self.submission_id = data.get("submission_id") - problem_id = data.get("problem_id") if self.submission_id: self.submission = Submission.objects.get(id=self.submission_id) self.contest_id = self.submission.contest_id @@ -114,6 +113,7 @@ def __init__(self, data): self.contest_id = data.get("contest_id") self.language = self.run_data["language"] + problem_id = data.get("problem_id") if self.contest_id: self.problem = Problem.objects.select_related("contest").get(id=problem_id, contest_id=self.contest_id) self.contest = self.problem.contest @@ -172,14 +172,6 @@ def judge(self): "io_mode": self.problem.io_mode } - if not self.submission_id: - data["test_case_id"] = None - data["output"] = True - data["test_case"] = [] - for testcases in self.run_data["new_testcase"]: - data["test_case"].append({"input": testcases, "output": ""}) - run_id = self.run_data["run_id"] - if self.submission_id: with ChooseJudgeServer() as server: if not server: @@ -233,42 +225,43 @@ def judge(self): # At this point, the judgment is over, try to process the remaining tasks in the task queue process_pending_task() else: + data["test_case_id"] = None + data["output"] = True + data["test_case"] = [] + for testcases in self.run_data["new_testcase"]: + data["test_case"].append({"input": testcases, "output": ""}) + run_id = self.run_data["run_id"] + with ChooseJudgeServer() as server: if not server: cache.lpush(CacheKey.run_waiting_queue, json.dumps(self.run_data)) return resp = self._request(urljoin(server.service_url, "/judge"), data=data) - outputs = [] - if not resp: # System error - outputs.append({"err": "System Error", "data": "System Error"}) - cache.hset("run", run_id, json.dumps(outputs)) + cache.hset("run", run_id, json.dumps([{"err": "System Error", "data": "System Error"}])) elif resp["err"]: # Compile error cache.hset("run", run_id, json.dumps(resp)) else: # Other errors or normal operation resp["data"].sort(key=lambda x: int(x["test_case"])) - output_data = resp["data"] - tc_num = len(output_data) - for i in range(tc_num): - output_ele = {} - output_ele["output"] = {} - output_ele["input"] = data["test_case"][i]["input"] - if output_data[i]["result"] in (-1, 0): - output_ele["output"]["err"] = None - output_ele["output"]["data"] = output_data[i]["output"] - + resp_data = resp["data"] + testcase_num = len(resp_data) + run_result = [] + for i in range(testcase_num): + result = {} + result["output"] = {} + result["input"] = data["test_case"][i]["input"] + if resp_data[i]["result"] in (-1, 0): + result["output"]["err"] = None + result["output"]["data"] = resp_data[i]["output"] else: - err_code = output_data[i]["result"] - err_type = ["1": "CPU Time Exceeded", "2": "Time Limit Exceeded", "3": "Memory Limit Exceeded", "4": "Runtime Error"] - output_ele["output"]["err"] = err_type[err_code] - output_ele["output"]["data"] = None - - outputs.append(output_ele) - - cache.hset("run", run_id, json.dumps(outputs)) + err_code = resp_data[i]["result"] + result["output"]["err"] = err_code + result["output"]["data"] = None + run_result.append(result) + cache.hset("run", run_id, json.dumps(run_result)) # At this point, the judgment is over, try to process the remaining tasks in the task queue process_pending_task_run() diff --git a/backend/submission/views.py b/backend/submission/views.py index 6254d01b7..b80a1c3d1 100644 --- a/backend/submission/views.py +++ b/backend/submission/views.py @@ -69,7 +69,7 @@ def post(self, request): def get(self, request): run_id = request.GET.get("run_id") if not cache.hexists("run", run_id): - return self.error("redis hash field error - such field doesn't exist") + return self.error("run_id does not exist") res = cache.hget("run", run_id) res = res.decode("utf-8") @@ -126,8 +126,8 @@ def post(self, request): ip=request.session["ip"], contest_id=data.get("contest_id")) # use this for debug - temp_data = {"submission_id": submission.id, "problem_id": problem.id} - judge_task.send(temp_data) + submission_info = {"submission_id": submission.id, "problem_id": problem.id} + judge_task.send(submission_info) if hide_id: return self.success() else: diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index ea0e79378..a73e107c2 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -501,7 +501,7 @@ export default { this.runResults = [] for (const runResult of data.data) { if (runResult.output.err) { - this.runResults.push({ input: runResult.input, output: runResult.output.err }) + this.runResults.push({ input: runResult.input, output: JUDGE_STATUS[runResult.output.err].name }) } else { this.runResults.push({ input: runResult.input, output: runResult.output.data }) } From 92d62ea1f9bcbbf1e52251ccae5a8014a4effbc1 Mon Sep 17 00:00:00 2001 From: Jimin Ha Date: Tue, 14 Sep 2021 00:36:32 +0900 Subject: [PATCH 21/22] Change testcase input into textarea & add overflow controls --- .../src/pages/oj/views/problem/Problem.vue | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index a73e107c2..a22b23488 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -93,27 +93,28 @@
- Add Testcase - - + Manage Testcase + + - Add Testcase - + Testcase Input + Add
- - + + - - + + @@ -787,6 +788,7 @@ export default { border-top: 1px solid #3B4F56; display: flex; flex-flow: column; + overflow-y: scroll; * { margin: 0; @@ -813,12 +815,13 @@ export default { .io-content-cell { padding: 10px 15px; - + overflow: auto; .sample-io { min-height: 40px; border-radius: 5px; background: #24272D; color: white; + white-space: pre; } } } From 5cd35da718eb75a0b92bd845b6a618b720d4ef12 Mon Sep 17 00:00:00 2001 From: HAKSAN Date: Thu, 14 Oct 2021 00:58:38 +0900 Subject: [PATCH 22/22] Fix infinite-loop issue --- backend/submission/views.py | 3 ++- frontend/src/pages/oj/views/problem/Problem.vue | 15 ++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/backend/submission/views.py b/backend/submission/views.py index b80a1c3d1..188e91242 100644 --- a/backend/submission/views.py +++ b/backend/submission/views.py @@ -74,7 +74,8 @@ def get(self, request): res = cache.hget("run", run_id) res = res.decode("utf-8") res = json.loads(res) - cache.hdel("run", run_id) + if res != "Judging": + cache.hdel("run", run_id) return self.success(res) diff --git a/frontend/src/pages/oj/views/problem/Problem.vue b/frontend/src/pages/oj/views/problem/Problem.vue index a22b23488..69794cdcd 100644 --- a/frontend/src/pages/oj/views/problem/Problem.vue +++ b/frontend/src/pages/oj/views/problem/Problem.vue @@ -511,12 +511,17 @@ export default { }, checkRunState (runID) { const checkStatus = async () => { - const res = await api.getRunResult(runID) - if (res.length !== 0 && res.error !== null && res.data.data !== 'Judging') { - this.saveRunResult(res.data) + try { + const res = await api.getRunResult(runID) + if (res.length !== 0 && res.error !== null && res.data.data !== 'Judging') { + this.saveRunResult(res.data) + clearTimeout(this.runRefresh) + } else { + this.runRefresh = setTimeout(checkStatus, 2000) + } + } catch (err) { + this.submitting = false clearTimeout(this.runRefresh) - } else { - this.runRefresh = setTimeout(checkStatus, 2000) } } this.runRefresh = setTimeout(checkStatus, 2000)