NDOJ/judge/bridge/judge_list.py

161 lines
5.6 KiB
Python
Raw Permalink Normal View History

2020-01-21 06:35:58 +00:00
import logging
from collections import namedtuple
from operator import attrgetter
from threading import RLock
2023-11-15 14:17:48 +00:00
from judge.bridge.utils import VanishedSubmission
2020-01-21 06:35:58 +00:00
try:
from llist import dllist
except ImportError:
from pyllist import dllist
2022-05-14 17:57:27 +00:00
logger = logging.getLogger("judge.bridge")
2020-01-21 06:35:58 +00:00
2022-05-14 17:57:27 +00:00
PriorityMarker = namedtuple("PriorityMarker", "priority")
2020-01-21 06:35:58 +00:00
class JudgeList(object):
priorities = 4
def __init__(self):
self.queue = dllist()
2022-05-14 17:57:27 +00:00
self.priority = [
self.queue.append(PriorityMarker(i)) for i in range(self.priorities)
]
2020-01-21 06:35:58 +00:00
self.judges = set()
self.node_map = {}
self.submission_map = {}
self.lock = RLock()
def _handle_free_judge(self, judge):
with self.lock:
node = self.queue.first
while node:
if not isinstance(node.value, PriorityMarker):
2020-07-19 21:27:14 +00:00
id, problem, language, source, judge_id = node.value
if judge.can_judge(problem, language, judge_id):
2020-01-21 06:35:58 +00:00
self.submission_map[id] = judge
2022-05-14 17:57:27 +00:00
logger.info(
"Dispatched queued submission %d: %s", id, judge.name
)
2020-01-21 06:35:58 +00:00
try:
judge.submit(id, problem, language, source)
2023-11-15 14:17:48 +00:00
except VanishedSubmission:
pass
2020-01-21 06:35:58 +00:00
except Exception:
2022-05-14 17:57:27 +00:00
logger.exception(
"Failed to dispatch %d (%s, %s) to %s",
id,
problem,
language,
judge.name,
)
2020-01-21 06:35:58 +00:00
self.judges.remove(judge)
return
self.queue.remove(node)
del self.node_map[id]
break
node = node.next
def register(self, judge):
with self.lock:
# Disconnect all judges with the same name, see <https://github.com/DMOJ/online-judge/issues/828>
self.disconnect(judge, force=True)
self.judges.add(judge)
self._handle_free_judge(judge)
def disconnect(self, judge_id, force=False):
2020-07-19 21:27:14 +00:00
with self.lock:
for judge in self.judges:
if judge.name == judge_id:
judge.disconnect(force=force)
2020-01-21 06:35:58 +00:00
def update_problems(self, judge):
with self.lock:
self._handle_free_judge(judge)
def remove(self, judge):
with self.lock:
sub = judge.get_current_submission()
if sub is not None:
try:
del self.submission_map[sub]
except KeyError:
pass
self.judges.discard(judge)
def __iter__(self):
return iter(self.judges)
def on_judge_free(self, judge, submission):
with self.lock:
2022-05-14 17:57:27 +00:00
logger.info("Judge available after grading %d: %s", submission, judge.name)
2020-01-21 06:35:58 +00:00
del self.submission_map[submission]
2020-07-19 21:27:14 +00:00
judge._working = False
judge._working_data = {}
2020-01-21 06:35:58 +00:00
self._handle_free_judge(judge)
def abort(self, submission):
with self.lock:
2022-05-14 17:57:27 +00:00
logger.info("Abort request: %d", submission)
2020-01-21 06:35:58 +00:00
try:
self.submission_map[submission].abort()
return True
except KeyError:
try:
node = self.node_map[submission]
except KeyError:
pass
else:
self.queue.remove(node)
del self.node_map[submission]
return False
def check_priority(self, priority):
return 0 <= priority < self.priorities
2020-07-19 21:27:14 +00:00
def judge(self, id, problem, language, source, judge_id, priority):
2020-01-21 06:35:58 +00:00
with self.lock:
if id in self.submission_map or id in self.node_map:
# Already judging, don't queue again. This can happen during batch rejudges, rejudges should be
# idempotent.
return
2020-07-19 21:27:14 +00:00
candidates = [
2022-05-14 17:57:27 +00:00
judge
for judge in self.judges
if not judge.working and judge.can_judge(problem, language, judge_id)
2020-07-19 21:27:14 +00:00
]
if judge_id:
2022-05-14 17:57:27 +00:00
logger.info(
"Specified judge %s is%savailable",
judge_id,
" " if candidates else " not ",
)
2020-07-19 21:27:14 +00:00
else:
2022-05-14 17:57:27 +00:00
logger.info("Free judges: %d", len(candidates))
2020-01-21 06:35:58 +00:00
if candidates:
# Schedule the submission on the judge reporting least load.
2022-05-14 17:57:27 +00:00
judge = min(candidates, key=attrgetter("load"))
logger.info("Dispatched submission %d to: %s", id, judge.name)
2020-01-21 06:35:58 +00:00
self.submission_map[id] = judge
try:
judge.submit(id, problem, language, source)
except Exception:
2022-05-14 17:57:27 +00:00
logger.exception(
"Failed to dispatch %d (%s, %s) to %s",
id,
problem,
language,
judge.name,
)
2020-01-21 06:35:58 +00:00
self.judges.discard(judge)
2020-07-19 21:27:14 +00:00
return self.judge(id, problem, language, source, judge_id, priority)
2020-01-21 06:35:58 +00:00
else:
2020-07-19 21:27:14 +00:00
self.node_map[id] = self.queue.insert(
(id, problem, language, source, judge_id),
self.priority[priority],
)
2022-05-14 17:57:27 +00:00
logger.info("Queued submission: %d", id)