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/signature.py b/dmoj/graders/signature.py index bc89da558..fc6da5c85 100644 --- a/dmoj/graders/signature.py +++ b/dmoj/graders/signature.py @@ -26,8 +26,10 @@ def _generate_binary(self) -> BaseExecutor: aux_sources[handler_data['header']] = header entry = entry_point - return executors[self.language].Executor( + executor = executors[self.language].Executor( self.problem.id, entry, aux_sources=aux_sources, defines=['-DSIGNATURE_GRADER'] ) + self._orig_fsize = executor.fsize + return executor else: raise InternalError('no valid runtime for signature grading %s found' % self.language) diff --git a/dmoj/graders/standard.py b/dmoj/graders/standard.py index d636ee544..c8925262e 100644 --- a/dmoj/graders/standard.py +++ b/dmoj/graders/standard.py @@ -4,6 +4,7 @@ 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 @@ -15,6 +16,11 @@ class StandardGrader(BaseGrader): + _stdout_io: MmapableIO + _stderr_io: MmapableIO + _orig_fsize: int + memfd_output: bool = True + def grade(self, case: TestCase) -> Result: result = Result(case) @@ -83,34 +89,54 @@ def check_result(self, case: TestCase, result: Result) -> CheckerOutput: return check def _launch_process(self, case: TestCase, input_file=None) -> None: + 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() + + 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, ) + self._orig_fsize = executor.fsize + return executor