|
1 | 1 | import logging |
2 | 2 | import subprocess |
| 3 | +from typing import Any |
3 | 4 |
|
4 | 5 | from dmoj.checkers import CheckerOutput |
5 | 6 | from dmoj.cptbox import TracedPopen |
6 | 7 | from dmoj.cptbox.lazy_bytes import LazyBytes |
| 8 | +from dmoj.cptbox.utils import MemoryIO, MmapableIO |
7 | 9 | from dmoj.error import OutputLimitExceeded |
8 | 10 | from dmoj.executors import executors |
9 | 11 | from dmoj.executors.base_executor import BaseExecutor |
|
15 | 17 |
|
16 | 18 |
|
17 | 19 | class StandardGrader(BaseGrader): |
| 20 | + _stdout_io: MmapableIO |
| 21 | + _stderr_io: MmapableIO |
| 22 | + _orig_fsize: int |
| 23 | + memfd_output: bool = True |
| 24 | + |
18 | 25 | def grade(self, case: TestCase) -> Result: |
19 | 26 | result = Result(case) |
20 | 27 |
|
@@ -83,34 +90,60 @@ def check_result(self, case: TestCase, result: Result) -> CheckerOutput: |
83 | 90 | return check |
84 | 91 |
|
85 | 92 | def _launch_process(self, case: TestCase, input_file=None) -> None: |
| 93 | + stdout: Any |
| 94 | + stderr: Any |
| 95 | + |
| 96 | + if self.memfd_output: |
| 97 | + stdout = self._stdout_io = MemoryIO() |
| 98 | + stderr = self._stderr_io = MemoryIO() |
| 99 | + self.binary.fsize = max(self._orig_fsize, case.config.output_limit_length + 1024, 1048576) |
| 100 | + else: |
| 101 | + stdout = subprocess.PIPE |
| 102 | + stderr = subprocess.PIPE |
| 103 | + |
86 | 104 | self._current_proc = self.binary.launch( |
87 | 105 | time=self.problem.time_limit, |
88 | 106 | memory=self.problem.memory_limit, |
89 | 107 | symlinks=case.config.symlinks, |
90 | 108 | stdin=input_file or subprocess.PIPE, |
91 | | - stdout=subprocess.PIPE, |
92 | | - stderr=subprocess.PIPE, |
| 109 | + stdout=stdout, |
| 110 | + stderr=stderr, |
93 | 111 | wall_time=case.config.wall_time_factor * self.problem.time_limit, |
94 | 112 | ) |
95 | 113 |
|
96 | 114 | def _interact_with_process(self, case: TestCase, result: Result) -> bytes: |
97 | 115 | process = self._current_proc |
98 | 116 | assert process is not None |
99 | | - try: |
100 | | - result.proc_output, error = process.communicate( |
101 | | - None, outlimit=case.config.output_limit_length, errlimit=1048576 |
102 | | - ) |
103 | | - except OutputLimitExceeded: |
104 | | - error = b'' |
105 | | - process.kill() |
106 | | - finally: |
| 117 | + |
| 118 | + if self.memfd_output: |
107 | 119 | process.wait() |
| 120 | + |
| 121 | + result.proc_output = self._stdout_io.to_bytes() |
| 122 | + self._stdout_io.close() |
| 123 | + |
| 124 | + if len(result.proc_output) > case.config.output_limit_length: |
| 125 | + process.mark_ole() |
| 126 | + |
| 127 | + error = self._stderr_io.to_bytes() |
| 128 | + self._stderr_io.close() |
| 129 | + else: |
| 130 | + try: |
| 131 | + result.proc_output, error = process.communicate( |
| 132 | + None, outlimit=case.config.output_limit_length, errlimit=1048576 |
| 133 | + ) |
| 134 | + except OutputLimitExceeded: |
| 135 | + error = b'' |
| 136 | + process.kill() |
| 137 | + finally: |
| 138 | + process.wait() |
108 | 139 | return error |
109 | 140 |
|
110 | 141 | def _generate_binary(self) -> BaseExecutor: |
111 | | - return executors[self.language].Executor( |
| 142 | + executor = executors[self.language].Executor( |
112 | 143 | self.problem.id, |
113 | 144 | self.source, |
114 | 145 | hints=self.problem.config.hints or [], |
115 | 146 | unbuffered=self.problem.config.unbuffered, |
116 | 147 | ) |
| 148 | + self._orig_fsize = executor.fsize |
| 149 | + return executor |
0 commit comments