diff --git a/dmoj/graders/interactive.py b/dmoj/graders/interactive.py index 59e33827a..e3ce794e2 100644 --- a/dmoj/graders/interactive.py +++ b/dmoj/graders/interactive.py @@ -104,6 +104,7 @@ def close(self) -> None: class InteractiveGrader(StandardGrader): check: CheckerOutput + memfd_output = False def _launch_process(self, case, input_file=None): super()._launch_process(case, input_file=None) diff --git a/dmoj/graders/standard.py b/dmoj/graders/standard.py index d636ee544..96dbf0a01 100644 --- a/dmoj/graders/standard.py +++ b/dmoj/graders/standard.py @@ -1,20 +1,32 @@ import logging import subprocess +from typing import Any from dmoj.checkers import CheckerOutput from dmoj.cptbox import TracedPopen from dmoj.cptbox.lazy_bytes import LazyBytes +from dmoj.cptbox.utils import MemoryIO, MmapableIO from dmoj.error import OutputLimitExceeded from dmoj.executors import executors from dmoj.executors.base_executor import BaseExecutor from dmoj.graders.base import BaseGrader -from dmoj.problem import TestCase +from dmoj.judge import JudgeWorker +from dmoj.problem import Problem, TestCase from dmoj.result import CheckerResult, Result log = logging.getLogger('dmoj.graders') class StandardGrader(BaseGrader): + _stdout_io: MmapableIO + _stderr_io: MmapableIO + _orig_fsize: int + memfd_output: bool = True + + def __init__(self, judge: 'JudgeWorker', problem: Problem, language: str, source: bytes) -> None: + super().__init__(judge, problem, language, source) + self._orig_fsize = self.binary.fsize + def grade(self, case: TestCase) -> Result: result = Result(case) @@ -83,34 +95,59 @@ def check_result(self, case: TestCase, result: Result) -> CheckerOutput: return check def _launch_process(self, case: TestCase, input_file=None) -> None: + stdout: Any + stderr: Any + + if self.memfd_output: + stdout = self._stdout_io = MemoryIO() + stderr = self._stderr_io = MemoryIO() + self.binary.fsize = max(self._orig_fsize, case.config.output_limit_length + 1024, 1048576) + else: + stdout = subprocess.PIPE + stderr = subprocess.PIPE + self._current_proc = self.binary.launch( time=self.problem.time_limit, memory=self.problem.memory_limit, symlinks=case.config.symlinks, stdin=input_file or subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + stdout=stdout, + stderr=stderr, wall_time=case.config.wall_time_factor * self.problem.time_limit, ) def _interact_with_process(self, case: TestCase, result: Result) -> bytes: process = self._current_proc assert process is not None - try: - result.proc_output, error = process.communicate( - None, outlimit=case.config.output_limit_length, errlimit=1048576 - ) - except OutputLimitExceeded: - error = b'' - process.kill() - finally: + + if self.memfd_output: process.wait() + + result.proc_output = self._stdout_io.to_bytes() + self._stdout_io.close() + + if len(result.proc_output) > case.config.output_limit_length: + process.mark_ole() + + error = self._stderr_io.to_bytes() + self._stderr_io.close() + else: + try: + result.proc_output, error = process.communicate( + None, outlimit=case.config.output_limit_length, errlimit=1048576 + ) + except OutputLimitExceeded: + error = b'' + process.kill() + finally: + process.wait() return error def _generate_binary(self) -> BaseExecutor: - return executors[self.language].Executor( + executor = executors[self.language].Executor( self.problem.id, self.source, hints=self.problem.config.hints or [], unbuffered=self.problem.config.unbuffered, ) + return executor diff --git a/dmoj/result.py b/dmoj/result.py index b42992c70..493710973 100644 --- a/dmoj/result.py +++ b/dmoj/result.py @@ -31,7 +31,7 @@ class Result: 'OLE': 'yellow', 'IE': 'red', } - CODE_DISPLAY_ORDER = ('IE', 'TLE', 'MLE', 'OLE', 'RTE', 'IR', 'WA', 'SC') + CODE_DISPLAY_ORDER = ('IE', 'OLE', 'TLE', 'MLE', 'RTE', 'IR', 'WA', 'SC') def __init__( self,