|
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