2020-07-19 21:27:14 +00:00
|
|
|
import hmac
|
2020-01-21 06:35:58 +00:00
|
|
|
import json
|
|
|
|
import logging
|
2020-07-19 21:27:14 +00:00
|
|
|
import threading
|
2020-01-21 06:35:58 +00:00
|
|
|
import time
|
2020-07-19 21:27:14 +00:00
|
|
|
from collections import deque, namedtuple
|
2020-01-21 06:35:58 +00:00
|
|
|
from operator import itemgetter
|
|
|
|
|
|
|
|
from django import db
|
2020-07-19 21:27:14 +00:00
|
|
|
from django.conf import settings
|
2020-01-21 06:35:58 +00:00
|
|
|
from django.utils import timezone
|
2020-06-08 03:36:23 +00:00
|
|
|
from django.db.models import F
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
from judge import event_poster as event
|
2020-07-19 21:27:14 +00:00
|
|
|
from judge.bridge.base_handler import ZlibPacketHandler, proxy_list
|
2020-01-21 06:35:58 +00:00
|
|
|
from judge.caching import finished_submission
|
|
|
|
from judge.models import Judge, Language, LanguageLimit, Problem, RuntimeVersion, Submission, SubmissionTestCase
|
|
|
|
|
|
|
|
logger = logging.getLogger('judge.bridge')
|
|
|
|
json_log = logging.getLogger('judge.json.bridge')
|
|
|
|
|
|
|
|
UPDATE_RATE_LIMIT = 5
|
|
|
|
UPDATE_RATE_TIME = 0.5
|
2020-07-19 21:27:14 +00:00
|
|
|
SubmissionData = namedtuple('SubmissionData', 'time memory short_circuit pretests_only contest_no attempt_no user_id')
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _ensure_connection():
|
|
|
|
try:
|
|
|
|
db.connection.cursor().execute('SELECT 1').fetchall()
|
|
|
|
except Exception:
|
|
|
|
db.connection.close()
|
|
|
|
|
|
|
|
|
2020-07-19 21:27:14 +00:00
|
|
|
class JudgeHandler(ZlibPacketHandler):
|
|
|
|
proxies = proxy_list(settings.BRIDGED_JUDGE_PROXIES or [])
|
|
|
|
|
|
|
|
def __init__(self, request, client_address, server, judges):
|
|
|
|
super().__init__(request, client_address, server)
|
|
|
|
|
|
|
|
self.judges = judges
|
|
|
|
self.handlers = {
|
|
|
|
'grading-begin': self.on_grading_begin,
|
|
|
|
'grading-end': self.on_grading_end,
|
|
|
|
'compile-error': self.on_compile_error,
|
|
|
|
'compile-message': self.on_compile_message,
|
|
|
|
'batch-begin': self.on_batch_begin,
|
|
|
|
'batch-end': self.on_batch_end,
|
|
|
|
'test-case-status': self.on_test_case,
|
|
|
|
'internal-error': self.on_internal_error,
|
|
|
|
'submission-terminated': self.on_submission_terminated,
|
|
|
|
'submission-acknowledged': self.on_submission_acknowledged,
|
|
|
|
'ping-response': self.on_ping_response,
|
|
|
|
'supported-problems': self.on_supported_problems,
|
|
|
|
'handshake': self.on_handshake,
|
|
|
|
}
|
|
|
|
self._working = False
|
|
|
|
self._no_response_job = None
|
|
|
|
self._problems = []
|
|
|
|
self.executors = {}
|
|
|
|
self.problems = {}
|
|
|
|
self.latency = None
|
|
|
|
self.time_delta = None
|
|
|
|
self.load = 1e100
|
|
|
|
self.name = None
|
|
|
|
self.batch_id = None
|
|
|
|
self.in_batch = False
|
|
|
|
self._stop_ping = threading.Event()
|
|
|
|
self._ping_average = deque(maxlen=6) # 1 minute average, just like load
|
|
|
|
self._time_delta = deque(maxlen=6)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
# each value is (updates, last reset)
|
|
|
|
self.update_counter = {}
|
|
|
|
self.judge = None
|
|
|
|
self.judge_address = None
|
|
|
|
|
|
|
|
self._submission_cache_id = None
|
|
|
|
self._submission_cache = {}
|
|
|
|
|
2020-07-19 21:27:14 +00:00
|
|
|
def on_connect(self):
|
|
|
|
self.timeout = 15
|
|
|
|
logger.info('Judge connected from: %s', self.client_address)
|
2020-01-21 06:35:58 +00:00
|
|
|
json_log.info(self._make_json_log(action='connect'))
|
|
|
|
|
2020-07-19 21:27:14 +00:00
|
|
|
def on_disconnect(self):
|
|
|
|
self._stop_ping.set()
|
|
|
|
if self._working:
|
|
|
|
logger.error('Judge %s disconnected while handling submission %s', self.name, self._working)
|
|
|
|
self.judges.remove(self)
|
|
|
|
if self.name is not None:
|
|
|
|
self._disconnected()
|
|
|
|
logger.info('Judge disconnected from: %s with name %s', self.client_address, self.name)
|
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
json_log.info(self._make_json_log(action='disconnect', info='judge disconnected'))
|
|
|
|
if self._working:
|
2020-07-19 21:27:14 +00:00
|
|
|
Submission.objects.filter(id=self._working).update(status='IE', result='IE', error='')
|
2020-01-21 06:35:58 +00:00
|
|
|
json_log.error(self._make_json_log(sub=self._working, action='close', info='IE due to shutdown on grading'))
|
|
|
|
|
2020-07-19 21:27:14 +00:00
|
|
|
def _authenticate(self, id, key):
|
2020-01-21 06:35:58 +00:00
|
|
|
try:
|
2020-07-19 21:27:14 +00:00
|
|
|
judge = Judge.objects.get(name=id, is_blocked=False)
|
|
|
|
except Judge.DoesNotExist:
|
|
|
|
result = False
|
|
|
|
else:
|
|
|
|
result = hmac.compare_digest(judge.auth_key, key)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
if not result:
|
|
|
|
json_log.warning(self._make_json_log(action='auth', judge=id, info='judge failed authentication'))
|
|
|
|
return result
|
|
|
|
|
|
|
|
def _connected(self):
|
|
|
|
judge = self.judge = Judge.objects.get(name=self.name)
|
|
|
|
judge.start_time = timezone.now()
|
|
|
|
judge.online = True
|
|
|
|
judge.problems.set(Problem.objects.filter(code__in=list(self.problems.keys())))
|
|
|
|
judge.runtimes.set(Language.objects.filter(key__in=list(self.executors.keys())))
|
|
|
|
|
|
|
|
# Delete now in case we somehow crashed and left some over from the last connection
|
|
|
|
RuntimeVersion.objects.filter(judge=judge).delete()
|
|
|
|
versions = []
|
|
|
|
for lang in judge.runtimes.all():
|
|
|
|
versions += [
|
|
|
|
RuntimeVersion(language=lang, name=name, version='.'.join(map(str, version)), priority=idx, judge=judge)
|
|
|
|
for idx, (name, version) in enumerate(self.executors[lang.key])
|
|
|
|
]
|
|
|
|
RuntimeVersion.objects.bulk_create(versions)
|
|
|
|
judge.last_ip = self.client_address[0]
|
|
|
|
judge.save()
|
|
|
|
self.judge_address = '[%s]:%s' % (self.client_address[0], self.client_address[1])
|
|
|
|
json_log.info(self._make_json_log(action='auth', info='judge successfully authenticated',
|
|
|
|
executors=list(self.executors.keys())))
|
|
|
|
|
|
|
|
def _disconnected(self):
|
|
|
|
Judge.objects.filter(id=self.judge.id).update(online=False)
|
|
|
|
RuntimeVersion.objects.filter(judge=self.judge).delete()
|
|
|
|
|
|
|
|
def _update_ping(self):
|
|
|
|
try:
|
|
|
|
Judge.objects.filter(name=self.name).update(ping=self.latency, load=self.load)
|
|
|
|
except Exception as e:
|
|
|
|
# What can I do? I don't want to tie this to MySQL.
|
|
|
|
if e.__class__.__name__ == 'OperationalError' and e.__module__ == '_mysql_exceptions' and e.args[0] == 2006:
|
|
|
|
db.connection.close()
|
|
|
|
|
2020-07-19 21:27:14 +00:00
|
|
|
def send(self, data):
|
|
|
|
super().send(json.dumps(data, separators=(',', ':')))
|
|
|
|
|
|
|
|
def on_handshake(self, packet):
|
|
|
|
if 'id' not in packet or 'key' not in packet:
|
|
|
|
logger.warning('Malformed handshake: %s', self.client_address)
|
|
|
|
self.close()
|
|
|
|
return
|
|
|
|
|
|
|
|
if not self._authenticate(packet['id'], packet['key']):
|
|
|
|
logger.warning('Authentication failure: %s', self.client_address)
|
|
|
|
self.close()
|
|
|
|
return
|
|
|
|
|
|
|
|
self.timeout = 60
|
|
|
|
self._problems = packet['problems']
|
|
|
|
self.problems = dict(self._problems)
|
|
|
|
self.executors = packet['executors']
|
|
|
|
self.name = packet['id']
|
|
|
|
|
|
|
|
self.send({'name': 'handshake-success'})
|
|
|
|
logger.info('Judge authenticated: %s (%s)', self.client_address, packet['id'])
|
|
|
|
self.judges.register(self)
|
|
|
|
threading.Thread(target=self._ping_thread).start()
|
|
|
|
self._connected()
|
|
|
|
|
|
|
|
def can_judge(self, problem, executor, judge_id=None):
|
|
|
|
return problem in self.problems and executor in self.executors and (not judge_id or self.name == judge_id)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def working(self):
|
|
|
|
return bool(self._working)
|
|
|
|
|
|
|
|
def get_related_submission_data(self, submission):
|
|
|
|
_ensure_connection()
|
|
|
|
|
|
|
|
try:
|
|
|
|
pid, time, memory, short_circuit, lid, is_pretested, sub_date, uid, part_virtual, part_id = (
|
|
|
|
Submission.objects.filter(id=submission)
|
|
|
|
.values_list('problem__id', 'problem__time_limit', 'problem__memory_limit',
|
|
|
|
'problem__short_circuit', 'language__id', 'is_pretested', 'date', 'user__id',
|
|
|
|
'contest__participation__virtual', 'contest__participation__id')).get()
|
|
|
|
except Submission.DoesNotExist:
|
|
|
|
logger.error('Submission vanished: %s', submission)
|
|
|
|
json_log.error(self._make_json_log(
|
|
|
|
sub=self._working, action='request',
|
|
|
|
info='submission vanished when fetching info',
|
|
|
|
))
|
|
|
|
return
|
|
|
|
|
|
|
|
attempt_no = Submission.objects.filter(problem__id=pid, contest__participation__id=part_id, user__id=uid,
|
|
|
|
date__lt=sub_date).exclude(status__in=('CE', 'IE')).count() + 1
|
|
|
|
|
|
|
|
try:
|
|
|
|
time, memory = (LanguageLimit.objects.filter(problem__id=pid, language__id=lid)
|
|
|
|
.values_list('time_limit', 'memory_limit').get())
|
|
|
|
except LanguageLimit.DoesNotExist:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return SubmissionData(
|
|
|
|
time=time,
|
|
|
|
memory=memory,
|
|
|
|
short_circuit=short_circuit,
|
|
|
|
pretests_only=is_pretested,
|
|
|
|
contest_no=part_virtual,
|
|
|
|
attempt_no=attempt_no,
|
|
|
|
user_id=uid,
|
|
|
|
)
|
|
|
|
|
|
|
|
def disconnect(self, force=False):
|
|
|
|
if force:
|
|
|
|
# Yank the power out.
|
|
|
|
self.close()
|
2020-01-21 06:35:58 +00:00
|
|
|
else:
|
2020-07-19 21:27:14 +00:00
|
|
|
self.send({'name': 'disconnect'})
|
|
|
|
|
|
|
|
def submit(self, id, problem, language, source):
|
|
|
|
data = self.get_related_submission_data(id)
|
|
|
|
self._working = id
|
|
|
|
self._no_response_job = threading.Timer(20, self._kill_if_no_response)
|
|
|
|
self.send({
|
|
|
|
'name': 'submission-request',
|
|
|
|
'submission-id': id,
|
|
|
|
'problem-id': problem,
|
|
|
|
'language': language,
|
|
|
|
'source': source,
|
|
|
|
'time-limit': data.time,
|
|
|
|
'memory-limit': data.memory,
|
|
|
|
'short-circuit': data.short_circuit,
|
|
|
|
'meta': {
|
|
|
|
'pretests-only': data.pretests_only,
|
|
|
|
'in-contest': data.contest_no,
|
|
|
|
'attempt-no': data.attempt_no,
|
|
|
|
'user': data.user_id,
|
|
|
|
},
|
|
|
|
})
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2020-07-19 21:27:14 +00:00
|
|
|
def _kill_if_no_response(self):
|
|
|
|
logger.error('Judge failed to acknowledge submission: %s: %s', self.name, self._working)
|
|
|
|
self.close()
|
|
|
|
|
|
|
|
def on_timeout(self):
|
|
|
|
if self.name:
|
|
|
|
logger.warning('Judge seems dead: %s: %s', self.name, self._working)
|
|
|
|
|
|
|
|
def malformed_packet(self, exception):
|
|
|
|
logger.exception('Judge sent malformed packet: %s', self.name)
|
|
|
|
super(JudgeHandler, self).malformed_packet(exception)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def on_submission_processing(self, packet):
|
2020-07-19 21:27:14 +00:00
|
|
|
_ensure_connection()
|
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
id = packet['submission-id']
|
|
|
|
if Submission.objects.filter(id=id).update(status='P', judged_on=self.judge):
|
|
|
|
event.post('sub_%s' % Submission.get_id_secret(id), {'type': 'processing'})
|
|
|
|
self._post_update_submission(id, 'processing')
|
|
|
|
json_log.info(self._make_json_log(packet, action='processing'))
|
|
|
|
else:
|
|
|
|
logger.warning('Unknown submission: %s', id)
|
|
|
|
json_log.error(self._make_json_log(packet, action='processing', info='unknown submission'))
|
|
|
|
|
|
|
|
def on_submission_wrong_acknowledge(self, packet, expected, got):
|
|
|
|
json_log.error(self._make_json_log(packet, action='processing', info='wrong-acknowledge', expected=expected))
|
2020-07-19 21:27:14 +00:00
|
|
|
Submission.objects.filter(id=expected).update(status='IE', result='IE', error=None)
|
|
|
|
Submission.objects.filter(id=got, status='QU').update(status='IE', result='IE', error=None)
|
|
|
|
|
|
|
|
def on_submission_acknowledged(self, packet):
|
|
|
|
if not packet.get('submission-id', None) == self._working:
|
|
|
|
logger.error('Wrong acknowledgement: %s: %s, expected: %s', self.name, packet.get('submission-id', None),
|
|
|
|
self._working)
|
|
|
|
self.on_submission_wrong_acknowledge(packet, self._working, packet.get('submission-id', None))
|
|
|
|
self.close()
|
|
|
|
logger.info('Submission acknowledged: %d', self._working)
|
|
|
|
if self._no_response_job:
|
|
|
|
self._no_response_job.cancel()
|
|
|
|
self._no_response_job = None
|
|
|
|
self.on_submission_processing(packet)
|
|
|
|
|
|
|
|
def abort(self):
|
|
|
|
self.send({'name': 'terminate-submission'})
|
|
|
|
|
|
|
|
def get_current_submission(self):
|
|
|
|
return self._working or None
|
|
|
|
|
|
|
|
def ping(self):
|
|
|
|
self.send({'name': 'ping', 'when': time.time()})
|
|
|
|
|
|
|
|
def on_packet(self, data):
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
data = json.loads(data)
|
|
|
|
if 'name' not in data:
|
|
|
|
raise ValueError
|
|
|
|
except ValueError:
|
|
|
|
self.on_malformed(data)
|
|
|
|
else:
|
|
|
|
handler = self.handlers.get(data['name'], self.on_malformed)
|
|
|
|
handler(data)
|
|
|
|
except Exception:
|
|
|
|
logger.exception('Error in packet handling (Judge-side): %s', self.name)
|
|
|
|
self._packet_exception()
|
|
|
|
# You can't crash here because you aren't so sure about the judges
|
|
|
|
# not being malicious or simply malforms. THIS IS A SERVER!
|
|
|
|
|
|
|
|
def _packet_exception(self):
|
|
|
|
json_log.exception(self._make_json_log(sub=self._working, info='packet processing exception'))
|
|
|
|
|
|
|
|
def _submission_is_batch(self, id):
|
|
|
|
if not Submission.objects.filter(id=id).update(batch=True):
|
|
|
|
logger.warning('Unknown submission: %s', id)
|
|
|
|
|
|
|
|
def on_supported_problems(self, packet):
|
|
|
|
logger.info('%s: Updated problem list', self.name)
|
|
|
|
self._problems = packet['problems']
|
|
|
|
self.problems = dict(self._problems)
|
|
|
|
if not self.working:
|
|
|
|
self.judges.update_problems(self)
|
|
|
|
|
|
|
|
self.judge.problems.set(Problem.objects.filter(code__in=list(self.problems.keys())))
|
|
|
|
json_log.info(self._make_json_log(action='update-problems', count=len(self.problems)))
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def on_grading_begin(self, packet):
|
2020-07-19 21:27:14 +00:00
|
|
|
logger.info('%s: Grading has begun on: %s', self.name, packet['submission-id'])
|
|
|
|
self.batch_id = None
|
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
if Submission.objects.filter(id=packet['submission-id']).update(
|
2020-07-19 21:27:14 +00:00
|
|
|
status='G', is_pretested=packet['pretested'],
|
|
|
|
current_testcase=1, points=0,
|
|
|
|
batch=False, judged_date=timezone.now()):
|
2020-01-21 06:35:58 +00:00
|
|
|
SubmissionTestCase.objects.filter(submission_id=packet['submission-id']).delete()
|
|
|
|
event.post('sub_%s' % Submission.get_id_secret(packet['submission-id']), {'type': 'grading-begin'})
|
|
|
|
self._post_update_submission(packet['submission-id'], 'grading-begin')
|
|
|
|
json_log.info(self._make_json_log(packet, action='grading-begin'))
|
|
|
|
else:
|
|
|
|
logger.warning('Unknown submission: %s', packet['submission-id'])
|
|
|
|
json_log.error(self._make_json_log(packet, action='grading-begin', info='unknown submission'))
|
|
|
|
|
|
|
|
def on_grading_end(self, packet):
|
2020-07-19 21:27:14 +00:00
|
|
|
logger.info('%s: Grading has ended on: %s', self.name, packet['submission-id'])
|
|
|
|
self._free_self(packet)
|
|
|
|
self.batch_id = None
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
submission = Submission.objects.get(id=packet['submission-id'])
|
|
|
|
except Submission.DoesNotExist:
|
|
|
|
logger.warning('Unknown submission: %s', packet['submission-id'])
|
|
|
|
json_log.error(self._make_json_log(packet, action='grading-end', info='unknown submission'))
|
|
|
|
return
|
|
|
|
|
|
|
|
time = 0
|
|
|
|
memory = 0
|
|
|
|
points = 0.0
|
|
|
|
total = 0
|
|
|
|
status = 0
|
|
|
|
status_codes = ['SC', 'AC', 'WA', 'MLE', 'TLE', 'IR', 'RTE', 'OLE']
|
|
|
|
batches = {} # batch number: (points, total)
|
|
|
|
|
|
|
|
for case in SubmissionTestCase.objects.filter(submission=submission):
|
|
|
|
time += case.time
|
|
|
|
if not case.batch:
|
|
|
|
points += case.points
|
|
|
|
total += case.total
|
|
|
|
else:
|
|
|
|
if case.batch in batches:
|
|
|
|
batches[case.batch][0] = min(batches[case.batch][0], case.points)
|
|
|
|
batches[case.batch][1] = max(batches[case.batch][1], case.total)
|
|
|
|
else:
|
|
|
|
batches[case.batch] = [case.points, case.total]
|
|
|
|
memory = max(memory, case.memory)
|
|
|
|
i = status_codes.index(case.status)
|
|
|
|
if i > status:
|
|
|
|
status = i
|
|
|
|
|
|
|
|
for i in batches:
|
|
|
|
points += batches[i][0]
|
|
|
|
total += batches[i][1]
|
|
|
|
|
|
|
|
points = round(points, 1)
|
|
|
|
total = round(total, 1)
|
|
|
|
submission.case_points = points
|
|
|
|
submission.case_total = total
|
|
|
|
|
|
|
|
problem = submission.problem
|
|
|
|
sub_points = round(points / total * problem.points if total > 0 else 0, 3)
|
|
|
|
if not problem.partial and sub_points != problem.points:
|
|
|
|
sub_points = 0
|
|
|
|
|
|
|
|
submission.status = 'D'
|
|
|
|
submission.time = time
|
|
|
|
submission.memory = memory
|
|
|
|
submission.points = sub_points
|
|
|
|
submission.result = status_codes[status]
|
|
|
|
submission.save()
|
|
|
|
|
|
|
|
json_log.info(self._make_json_log(
|
|
|
|
packet, action='grading-end', time=time, memory=memory,
|
|
|
|
points=sub_points, total=problem.points, result=submission.result,
|
|
|
|
case_points=points, case_total=total, user=submission.user_id,
|
|
|
|
problem=problem.code, finish=True,
|
|
|
|
))
|
|
|
|
|
|
|
|
submission.user._updating_stats_only = True
|
|
|
|
submission.user.calculate_points()
|
|
|
|
problem._updating_stats_only = True
|
|
|
|
problem.update_stats()
|
|
|
|
submission.update_contest()
|
|
|
|
|
|
|
|
finished_submission(submission)
|
|
|
|
|
|
|
|
event.post('sub_%s' % submission.id_secret, {
|
|
|
|
'type': 'grading-end',
|
|
|
|
'time': time,
|
|
|
|
'memory': memory,
|
|
|
|
'points': float(points),
|
|
|
|
'total': float(problem.points),
|
|
|
|
'result': submission.result,
|
|
|
|
})
|
|
|
|
if hasattr(submission, 'contest'):
|
|
|
|
participation = submission.contest.participation
|
|
|
|
event.post('contest_%d' % participation.contest_id, {'type': 'update'})
|
|
|
|
self._post_update_submission(submission.id, 'grading-end', done=True)
|
|
|
|
|
|
|
|
def on_compile_error(self, packet):
|
2020-07-19 21:27:14 +00:00
|
|
|
logger.info('%s: Submission failed to compile: %s', self.name, packet['submission-id'])
|
|
|
|
self._free_self(packet)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
if Submission.objects.filter(id=packet['submission-id']).update(status='CE', result='CE', error=packet['log']):
|
|
|
|
event.post('sub_%s' % Submission.get_id_secret(packet['submission-id']), {
|
|
|
|
'type': 'compile-error',
|
|
|
|
'log': packet['log'],
|
|
|
|
})
|
|
|
|
self._post_update_submission(packet['submission-id'], 'compile-error', done=True)
|
|
|
|
json_log.info(self._make_json_log(packet, action='compile-error', log=packet['log'],
|
|
|
|
finish=True, result='CE'))
|
|
|
|
else:
|
|
|
|
logger.warning('Unknown submission: %s', packet['submission-id'])
|
|
|
|
json_log.error(self._make_json_log(packet, action='compile-error', info='unknown submission',
|
|
|
|
log=packet['log'], finish=True, result='CE'))
|
|
|
|
|
|
|
|
def on_compile_message(self, packet):
|
2020-07-19 21:27:14 +00:00
|
|
|
logger.info('%s: Submission generated compiler messages: %s', self.name, packet['submission-id'])
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
if Submission.objects.filter(id=packet['submission-id']).update(error=packet['log']):
|
|
|
|
event.post('sub_%s' % Submission.get_id_secret(packet['submission-id']), {'type': 'compile-message'})
|
|
|
|
json_log.info(self._make_json_log(packet, action='compile-message', log=packet['log']))
|
|
|
|
else:
|
|
|
|
logger.warning('Unknown submission: %s', packet['submission-id'])
|
|
|
|
json_log.error(self._make_json_log(packet, action='compile-message', info='unknown submission',
|
|
|
|
log=packet['log']))
|
|
|
|
|
|
|
|
def on_internal_error(self, packet):
|
2020-07-19 21:27:14 +00:00
|
|
|
try:
|
|
|
|
raise ValueError('\n\n' + packet['message'])
|
|
|
|
except ValueError:
|
|
|
|
logger.exception('Judge %s failed while handling submission %s', self.name, packet['submission-id'])
|
|
|
|
self._free_self(packet)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
id = packet['submission-id']
|
|
|
|
if Submission.objects.filter(id=id).update(status='IE', result='IE', error=packet['message']):
|
|
|
|
event.post('sub_%s' % Submission.get_id_secret(id), {'type': 'internal-error'})
|
|
|
|
self._post_update_submission(id, 'internal-error', done=True)
|
|
|
|
json_log.info(self._make_json_log(packet, action='internal-error', message=packet['message'],
|
|
|
|
finish=True, result='IE'))
|
|
|
|
else:
|
|
|
|
logger.warning('Unknown submission: %s', id)
|
|
|
|
json_log.error(self._make_json_log(packet, action='internal-error', info='unknown submission',
|
|
|
|
message=packet['message'], finish=True, result='IE'))
|
|
|
|
|
|
|
|
def on_submission_terminated(self, packet):
|
2020-07-19 21:27:14 +00:00
|
|
|
logger.info('%s: Submission aborted: %s', self.name, packet['submission-id'])
|
|
|
|
self._free_self(packet)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
if Submission.objects.filter(id=packet['submission-id']).update(status='AB', result='AB'):
|
|
|
|
event.post('sub_%s' % Submission.get_id_secret(packet['submission-id']), {'type': 'aborted-submission'})
|
|
|
|
self._post_update_submission(packet['submission-id'], 'terminated', done=True)
|
|
|
|
json_log.info(self._make_json_log(packet, action='aborted', finish=True, result='AB'))
|
|
|
|
else:
|
|
|
|
logger.warning('Unknown submission: %s', packet['submission-id'])
|
|
|
|
json_log.error(self._make_json_log(packet, action='aborted', info='unknown submission',
|
|
|
|
finish=True, result='AB'))
|
|
|
|
|
|
|
|
def on_batch_begin(self, packet):
|
2020-07-19 21:27:14 +00:00
|
|
|
logger.info('%s: Batch began on: %s', self.name, packet['submission-id'])
|
|
|
|
self.in_batch = True
|
|
|
|
if self.batch_id is None:
|
|
|
|
self.batch_id = 0
|
|
|
|
self._submission_is_batch(packet['submission-id'])
|
|
|
|
self.batch_id += 1
|
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
json_log.info(self._make_json_log(packet, action='batch-begin', batch=self.batch_id))
|
|
|
|
|
|
|
|
def on_batch_end(self, packet):
|
2020-07-19 21:27:14 +00:00
|
|
|
self.in_batch = False
|
|
|
|
logger.info('%s: Batch ended on: %s', self.name, packet['submission-id'])
|
2020-01-21 06:35:58 +00:00
|
|
|
json_log.info(self._make_json_log(packet, action='batch-end', batch=self.batch_id))
|
|
|
|
|
|
|
|
def on_test_case(self, packet, max_feedback=SubmissionTestCase._meta.get_field('feedback').max_length):
|
2020-07-19 21:27:14 +00:00
|
|
|
logger.info('%s: %d test case(s) completed on: %s', self.name, len(packet['cases']), packet['submission-id'])
|
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
id = packet['submission-id']
|
|
|
|
updates = packet['cases']
|
|
|
|
max_position = max(map(itemgetter('position'), updates))
|
2020-06-08 03:36:23 +00:00
|
|
|
sum_points = sum(map(itemgetter('points'), updates))
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2020-07-19 21:27:14 +00:00
|
|
|
|
2020-06-08 03:36:23 +00:00
|
|
|
if not Submission.objects.filter(id=id).update(current_testcase=max_position + 1, points=F('points') + sum_points):
|
2020-01-21 06:35:58 +00:00
|
|
|
logger.warning('Unknown submission: %s', id)
|
|
|
|
json_log.error(self._make_json_log(packet, action='test-case', info='unknown submission'))
|
|
|
|
return
|
|
|
|
|
|
|
|
bulk_test_case_updates = []
|
|
|
|
for result in updates:
|
|
|
|
test_case = SubmissionTestCase(submission_id=id, case=result['position'])
|
|
|
|
status = result['status']
|
|
|
|
if status & 4:
|
|
|
|
test_case.status = 'TLE'
|
|
|
|
elif status & 8:
|
|
|
|
test_case.status = 'MLE'
|
|
|
|
elif status & 64:
|
|
|
|
test_case.status = 'OLE'
|
|
|
|
elif status & 2:
|
|
|
|
test_case.status = 'RTE'
|
|
|
|
elif status & 16:
|
|
|
|
test_case.status = 'IR'
|
|
|
|
elif status & 1:
|
|
|
|
test_case.status = 'WA'
|
|
|
|
elif status & 32:
|
|
|
|
test_case.status = 'SC'
|
|
|
|
else:
|
|
|
|
test_case.status = 'AC'
|
|
|
|
test_case.time = result['time']
|
|
|
|
test_case.memory = result['memory']
|
|
|
|
test_case.points = result['points']
|
|
|
|
test_case.total = result['total-points']
|
|
|
|
test_case.batch = self.batch_id if self.in_batch else None
|
|
|
|
test_case.feedback = (result.get('feedback') or '')[:max_feedback]
|
|
|
|
test_case.extended_feedback = result.get('extended-feedback') or ''
|
|
|
|
test_case.output = result['output']
|
|
|
|
bulk_test_case_updates.append(test_case)
|
|
|
|
|
|
|
|
json_log.info(self._make_json_log(
|
|
|
|
packet, action='test-case', case=test_case.case, batch=test_case.batch,
|
|
|
|
time=test_case.time, memory=test_case.memory, feedback=test_case.feedback,
|
|
|
|
extended_feedback=test_case.extended_feedback, output=test_case.output,
|
|
|
|
points=test_case.points, total=test_case.total, status=test_case.status,
|
|
|
|
))
|
|
|
|
|
|
|
|
do_post = True
|
|
|
|
|
|
|
|
if id in self.update_counter:
|
|
|
|
cnt, reset = self.update_counter[id]
|
|
|
|
cnt += 1
|
|
|
|
if time.monotonic() - reset > UPDATE_RATE_TIME:
|
|
|
|
del self.update_counter[id]
|
|
|
|
else:
|
|
|
|
self.update_counter[id] = (cnt, reset)
|
|
|
|
if cnt > UPDATE_RATE_LIMIT:
|
|
|
|
do_post = False
|
|
|
|
if id not in self.update_counter:
|
|
|
|
self.update_counter[id] = (1, time.monotonic())
|
|
|
|
|
|
|
|
if do_post:
|
|
|
|
event.post('sub_%s' % Submission.get_id_secret(id), {
|
|
|
|
'type': 'test-case',
|
|
|
|
'id': max_position,
|
|
|
|
})
|
|
|
|
self._post_update_submission(id, state='test-case')
|
|
|
|
|
|
|
|
SubmissionTestCase.objects.bulk_create(bulk_test_case_updates)
|
|
|
|
|
2020-07-19 21:27:14 +00:00
|
|
|
def on_malformed(self, packet):
|
|
|
|
logger.error('%s: Malformed packet: %s', self.name, packet)
|
|
|
|
json_log.exception(self._make_json_log(sub=self._working, info='malformed json packet'))
|
|
|
|
|
|
|
|
def on_ping_response(self, packet):
|
|
|
|
end = time.time()
|
|
|
|
self._ping_average.append(end - packet['when'])
|
|
|
|
self._time_delta.append((end + packet['when']) / 2 - packet['time'])
|
|
|
|
self.latency = sum(self._ping_average) / len(self._ping_average)
|
|
|
|
self.time_delta = sum(self._time_delta) / len(self._time_delta)
|
|
|
|
self.load = packet['load']
|
|
|
|
self._update_ping()
|
|
|
|
|
|
|
|
def _free_self(self, packet):
|
|
|
|
self.judges.on_judge_free(self, packet['submission-id'])
|
|
|
|
|
|
|
|
def _ping_thread(self):
|
|
|
|
try:
|
|
|
|
while True:
|
|
|
|
self.ping()
|
|
|
|
if self._stop_ping.wait(10):
|
|
|
|
break
|
|
|
|
except Exception:
|
|
|
|
logger.exception('Ping error in %s', self.name)
|
|
|
|
self.close()
|
|
|
|
raise
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def _make_json_log(self, packet=None, sub=None, **kwargs):
|
|
|
|
data = {
|
|
|
|
'judge': self.name,
|
|
|
|
'address': self.judge_address,
|
|
|
|
}
|
|
|
|
if sub is None and packet is not None:
|
|
|
|
sub = packet.get('submission-id')
|
|
|
|
if sub is not None:
|
|
|
|
data['submission'] = sub
|
|
|
|
data.update(kwargs)
|
|
|
|
return json.dumps(data)
|
2020-07-19 21:27:14 +00:00
|
|
|
|
|
|
|
def _post_update_submission(self, id, state, done=False):
|
|
|
|
if self._submission_cache_id == id:
|
|
|
|
data = self._submission_cache
|
|
|
|
else:
|
|
|
|
self._submission_cache = data = Submission.objects.filter(id=id).values(
|
|
|
|
'problem__is_public', 'contest_object__key',
|
|
|
|
'user_id', 'problem_id', 'status', 'language__key',
|
|
|
|
).get()
|
|
|
|
self._submission_cache_id = id
|
|
|
|
|
|
|
|
if data['problem__is_public']:
|
|
|
|
event.post('submissions', {
|
|
|
|
'type': 'done-submission' if done else 'update-submission',
|
|
|
|
'state': state, 'id': id,
|
|
|
|
'contest': data['contest_object__key'],
|
|
|
|
'user': data['user_id'], 'problem': data['problem_id'],
|
|
|
|
'status': data['status'], 'language': data['language__key'],
|
|
|
|
})
|