159 lines
5.4 KiB
Python
159 lines
5.4 KiB
Python
import json
|
|
import logging
|
|
import socket
|
|
import struct
|
|
import zlib
|
|
|
|
from django.conf import settings
|
|
|
|
from judge import event_poster as event
|
|
|
|
logger = logging.getLogger("judge.judgeapi")
|
|
size_pack = struct.Struct("!I")
|
|
|
|
|
|
def _post_update_submission(submission, done=False):
|
|
if submission.problem.is_public:
|
|
event.post(
|
|
"submissions",
|
|
{
|
|
"type": "done-submission" if done else "update-submission",
|
|
"id": submission.id,
|
|
"contest": submission.contest_key,
|
|
"user": submission.user_id,
|
|
"problem": submission.problem_id,
|
|
"status": submission.status,
|
|
"language": submission.language.key,
|
|
},
|
|
)
|
|
|
|
|
|
def judge_request(packet, reply=True):
|
|
sock = socket.create_connection(
|
|
settings.BRIDGED_DJANGO_CONNECT or settings.BRIDGED_DJANGO_ADDRESS[0]
|
|
)
|
|
|
|
output = json.dumps(packet, separators=(",", ":"))
|
|
output = zlib.compress(output.encode("utf-8"))
|
|
writer = sock.makefile("wb")
|
|
writer.write(size_pack.pack(len(output)))
|
|
writer.write(output)
|
|
writer.close()
|
|
|
|
if reply:
|
|
reader = sock.makefile("rb", -1)
|
|
input = reader.read(size_pack.size)
|
|
if not input:
|
|
raise ValueError("Judge did not respond")
|
|
length = size_pack.unpack(input)[0]
|
|
input = reader.read(length)
|
|
if not input:
|
|
raise ValueError("Judge did not respond")
|
|
reader.close()
|
|
sock.close()
|
|
|
|
result = json.loads(zlib.decompress(input).decode("utf-8"))
|
|
return result
|
|
|
|
|
|
def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=None, delete_testcases=True):
|
|
from .models import ContestSubmission, Submission, SubmissionTestCase
|
|
|
|
CONTEST_SUBMISSION_PRIORITY = 0
|
|
DEFAULT_PRIORITY = 1
|
|
REJUDGE_PRIORITY = 2
|
|
BATCH_REJUDGE_PRIORITY = 3
|
|
|
|
updates = {
|
|
"time": None,
|
|
"memory": None,
|
|
"points": None,
|
|
"result": None,
|
|
"error": None,
|
|
"was_rejudged": rejudge,
|
|
"status": "QU",
|
|
}
|
|
try:
|
|
# This is set proactively; it might get unset in judgecallback's on_grading_begin if the problem doesn't
|
|
# actually have pretests stored on the judge.
|
|
updates["is_pretested"] = all(
|
|
ContestSubmission.objects.filter(submission=submission).values_list(
|
|
"problem__contest__run_pretests_only", "problem__is_pretested"
|
|
)[0]
|
|
)
|
|
except IndexError:
|
|
priority = DEFAULT_PRIORITY
|
|
else:
|
|
priority = CONTEST_SUBMISSION_PRIORITY
|
|
|
|
# This should prevent double rejudge issues by permitting only the judging of
|
|
# QU (which is the initial state) and D (which is the final state).
|
|
# Even though the bridge will not queue a submission already being judged,
|
|
# we will destroy the current state by deleting all SubmissionTestCase objects.
|
|
# However, we can't drop the old state immediately before a submission is set for judging,
|
|
# as that would prevent people from knowing a submission is being scheduled for rejudging.
|
|
# It is worth noting that this mechanism does not prevent a new rejudge from being scheduled
|
|
# while already queued, but that does not lead to data corruption.
|
|
if (
|
|
not Submission.objects.filter(id=submission.id)
|
|
.exclude(status__in=("P", "G"))
|
|
.update(**updates)
|
|
):
|
|
return False
|
|
|
|
if delete_testcases:
|
|
SubmissionTestCase.objects.filter(submission_id=submission.id).delete()
|
|
|
|
try:
|
|
response = judge_request(
|
|
{
|
|
"name": "submission-request",
|
|
"submission-id": submission.id,
|
|
"problem-id": submission.problem.code,
|
|
"language": submission.language.key,
|
|
"source": submission.source.source,
|
|
"judge-id": judge_id,
|
|
"priority": BATCH_REJUDGE_PRIORITY
|
|
if batch_rejudge
|
|
else REJUDGE_PRIORITY
|
|
if rejudge
|
|
else priority,
|
|
}
|
|
)
|
|
except BaseException:
|
|
logger.exception("Failed to send request to judge")
|
|
Submission.objects.filter(id=submission.id).update(status="IE", result="IE")
|
|
success = False
|
|
else:
|
|
if (
|
|
response["name"] != "submission-received"
|
|
or response["submission-id"] != submission.id
|
|
):
|
|
Submission.objects.filter(id=submission.id).update(status="IE", result="IE")
|
|
_post_update_submission(submission)
|
|
success = True
|
|
return success
|
|
|
|
|
|
def disconnect_judge(judge, force=False):
|
|
judge_request(
|
|
{"name": "disconnect-judge", "judge-id": judge.name, "force": force},
|
|
reply=False,
|
|
)
|
|
|
|
|
|
def abort_submission(submission):
|
|
from .models import Submission
|
|
|
|
response = judge_request(
|
|
{"name": "terminate-submission", "submission-id": submission.id}
|
|
)
|
|
# This defaults to true, so that in the case the JudgeList fails to remove the submission from the queue,
|
|
# and returns a bad-request, the submission is not falsely shown as "Aborted" when it will still be judged.
|
|
if not response.get("judge-aborted", True):
|
|
Submission.objects.filter(id=submission.id).update(status="AB", result="AB")
|
|
event.post(
|
|
"sub_%s" % Submission.get_id_secret(submission.id),
|
|
{"type": "aborted-submission"},
|
|
)
|
|
_post_update_submission(submission, done=True)
|