rbx.cp 0.13.4__py3-none-any.whl → 0.13.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rbx/box/checkers.py +2 -9
- rbx/box/cli.py +0 -1
- rbx/box/code.py +27 -80
- rbx/box/environment.py +16 -6
- rbx/box/generators.py +26 -3
- rbx/box/global_package.py +1 -1
- rbx/box/header.py +26 -8
- rbx/box/package.py +0 -14
- rbx/box/setter_config.py +11 -0
- rbx/box/solutions.py +12 -4
- rbx/box/tasks.py +9 -4
- rbx/box/testing/testing_package.py +69 -2
- rbx/box/ui/screens/run_explorer.py +0 -8
- rbx/box/ui/utils/run_ui.py +7 -3
- rbx/box/ui/widgets/test_output_box.py +1 -1
- rbx/box/unit.py +4 -4
- rbx/box/validators.py +3 -1
- rbx/grading/caching.py +65 -15
- rbx/grading/judge/cacher.py +5 -3
- rbx/grading/judge/program.py +300 -0
- rbx/grading/judge/sandbox.py +30 -200
- rbx/grading/judge/sandboxes/stupid_sandbox.py +234 -240
- rbx/grading/judge/sandboxes/tee.py +31 -0
- rbx/grading/judge/storage.py +7 -1
- rbx/grading/steps.py +89 -201
- rbx/grading/steps_with_caching.py +15 -6
- rbx/resources/presets/default/problem/problem.rbx.yml +0 -2
- rbx/resources/templates/rbx.h +43 -2
- rbx/testing_utils.py +7 -0
- rbx/utils.py +104 -6
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.6.dist-info}/METADATA +1 -1
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.6.dist-info}/RECORD +35 -40
- rbx/grading/judge/sandboxes/isolate.py +0 -695
- rbx/grading/judge/sandboxes/timeit.py +0 -358
- rbx/grading/judge/test.py +0 -38
- rbx/grading/judge/testiso.py +0 -54
- rbx/grading/processing_context.py +0 -71
- rbx/resources/envs/isolate.rbx.yml +0 -36
- rbx/resources/presets/default/problem/sols/slow.cpp +0 -15
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.6.dist-info}/LICENSE +0 -0
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.6.dist-info}/WHEEL +0 -0
- {rbx_cp-0.13.4.dist-info → rbx_cp-0.13.6.dist-info}/entry_points.txt +0 -0
@@ -1,358 +0,0 @@
|
|
1
|
-
import dataclasses
|
2
|
-
import os
|
3
|
-
import pathlib
|
4
|
-
import resource
|
5
|
-
import signal
|
6
|
-
import stat
|
7
|
-
import sys
|
8
|
-
import threading
|
9
|
-
from time import monotonic
|
10
|
-
from typing import Any, Dict, List, Optional, Set, Union
|
11
|
-
|
12
|
-
|
13
|
-
@dataclasses.dataclass
|
14
|
-
class Options:
|
15
|
-
output_file: str
|
16
|
-
argv: List[str]
|
17
|
-
chdir: Optional[str] = None
|
18
|
-
stdin_file: Optional[str] = None
|
19
|
-
stdout_file: Optional[str] = None
|
20
|
-
stderr_file: Optional[str] = None
|
21
|
-
time_limit: Optional[float] = None
|
22
|
-
wall_time_limit: Optional[float] = None # seconds
|
23
|
-
memory_limit: Optional[int] = None # kb, but passed in args as mb
|
24
|
-
fs_limit: Optional[int] = None # kb
|
25
|
-
files_to_open: List[int] = dataclasses.field(default_factory=list)
|
26
|
-
file_duplicates: Dict[int, List[str]] = dataclasses.field(default_factory=dict)
|
27
|
-
prefixed: Set[str] = dataclasses.field(default_factory=set)
|
28
|
-
prefix: str = ''
|
29
|
-
process_group: Optional[int] = None
|
30
|
-
|
31
|
-
|
32
|
-
def exit_with(code: int):
|
33
|
-
sys.exit(code)
|
34
|
-
|
35
|
-
|
36
|
-
def get_tee_command(files: List[str]) -> str:
|
37
|
-
path = (
|
38
|
-
os.path.join(os.path.dirname(os.path.realpath(__file__)), 'tee.py')
|
39
|
-
+ ' '
|
40
|
-
+ ' '.join(files)
|
41
|
-
)
|
42
|
-
return sys.executable + ' ' + path
|
43
|
-
|
44
|
-
|
45
|
-
valid_modes = ['a', 'w']
|
46
|
-
|
47
|
-
|
48
|
-
@dataclasses.dataclass
|
49
|
-
class Tee:
|
50
|
-
file: Any
|
51
|
-
prefix: Union[str, bytes] = ''
|
52
|
-
|
53
|
-
|
54
|
-
def create_tee(files, mode, buffer_size=4096, prefix=''):
|
55
|
-
"""Get a file object that will mirror writes across multiple files objs
|
56
|
-
|
57
|
-
Options:
|
58
|
-
files A list of files and/or file objects. All strings will be
|
59
|
-
treated as file paths and opened for writing. Everything
|
60
|
-
else is assumed to be a file-like object that implements
|
61
|
-
both the write() and flush() methods.
|
62
|
-
|
63
|
-
mode Which mode to use when opening new files. Valid values
|
64
|
-
are 'a' (append) and 'w' (overwrite).
|
65
|
-
|
66
|
-
buffer_size
|
67
|
-
Control the size of the buffer between writes to the
|
68
|
-
resulting file object and the list of files.
|
69
|
-
"""
|
70
|
-
if mode not in valid_modes:
|
71
|
-
raise IOError(
|
72
|
-
'Only valid modes to create_tee() are: %s' % ', '.join(valid_modes)
|
73
|
-
)
|
74
|
-
|
75
|
-
tee_list = []
|
76
|
-
for file in files:
|
77
|
-
if isinstance(file, Tee):
|
78
|
-
tee_list.append(file)
|
79
|
-
else:
|
80
|
-
tee_list.append(Tee(file))
|
81
|
-
for tee in tee_list:
|
82
|
-
if isinstance(tee.file, str):
|
83
|
-
tee.file = open(tee.file, f'{mode}b')
|
84
|
-
if isinstance(tee.prefix, str):
|
85
|
-
tee.prefix = tee.prefix.encode()
|
86
|
-
|
87
|
-
pipe_read, pipe_write = os.pipe()
|
88
|
-
pid = os.fork()
|
89
|
-
if pid == 0:
|
90
|
-
# Child -- Read bytes from the pipe and write them to the specified
|
91
|
-
# files.
|
92
|
-
try:
|
93
|
-
# Close parent's end of the pipe
|
94
|
-
os.close(pipe_write)
|
95
|
-
|
96
|
-
new = True
|
97
|
-
while bytes := os.read(pipe_read, 1):
|
98
|
-
for tee in tee_list:
|
99
|
-
if tee.prefix and new:
|
100
|
-
tee.file.write(tee.prefix)
|
101
|
-
tee.file.write(bytes)
|
102
|
-
tee.file.flush()
|
103
|
-
# TODO maybe add in fsync() here if the fileno() method
|
104
|
-
# exists on file
|
105
|
-
new = bytes == b'\n'
|
106
|
-
except Exception:
|
107
|
-
pass
|
108
|
-
finally:
|
109
|
-
os._exit(255)
|
110
|
-
else:
|
111
|
-
# Parent -- Return a file object wrapper around the pipe to the
|
112
|
-
# child.
|
113
|
-
# Preserve line buffering (buffering=1).
|
114
|
-
return os.fdopen(pipe_write, 'w', buffering=1, closefd=False)
|
115
|
-
|
116
|
-
|
117
|
-
def parse_opts() -> Options:
|
118
|
-
options = Options(output_file=sys.argv[1], argv=[])
|
119
|
-
options.files_to_open = []
|
120
|
-
num_opts = 0
|
121
|
-
while num_opts + 2 < len(sys.argv) and sys.argv[num_opts + 2].startswith('-'):
|
122
|
-
# Process option
|
123
|
-
opt = sys.argv[num_opts + 2]
|
124
|
-
if opt.startswith('-t'):
|
125
|
-
options.time_limit = float(opt[2:])
|
126
|
-
elif opt.startswith('-w'):
|
127
|
-
options.wall_time_limit = float(opt[2:])
|
128
|
-
elif opt.startswith('-m'):
|
129
|
-
options.memory_limit = int(opt[2:]) * 1024
|
130
|
-
elif opt.startswith('-i'):
|
131
|
-
options.stdin_file = opt[2:]
|
132
|
-
options.files_to_open.append(0)
|
133
|
-
elif opt.startswith('-o'):
|
134
|
-
options.stdout_file = opt[2:]
|
135
|
-
options.files_to_open.append(1)
|
136
|
-
elif opt.startswith('-e'):
|
137
|
-
options.stderr_file = opt[2:]
|
138
|
-
options.files_to_open.append(2)
|
139
|
-
elif opt.startswith('-d') or opt.startswith('-D'):
|
140
|
-
is_prefixed = opt.startswith('-D')
|
141
|
-
possibilities = [None, 'o', 'e']
|
142
|
-
index = possibilities.index(opt[2])
|
143
|
-
if index not in options.file_duplicates:
|
144
|
-
options.file_duplicates[index] = []
|
145
|
-
options.file_duplicates[index].append(opt[3:])
|
146
|
-
if is_prefixed:
|
147
|
-
options.prefixed.add(opt[3:])
|
148
|
-
elif opt.startswith('-c'):
|
149
|
-
options.chdir = opt[2:]
|
150
|
-
elif opt.startswith('-f'):
|
151
|
-
options.fs_limit = int(opt[2:])
|
152
|
-
elif opt.startswith('-P'):
|
153
|
-
options.prefix = opt[2:]
|
154
|
-
elif opt.startswith('-g'):
|
155
|
-
options.process_group = int(opt[2:])
|
156
|
-
else:
|
157
|
-
raise Exception(f'Invalid option {opt}')
|
158
|
-
num_opts += 1
|
159
|
-
options.argv = sys.argv[num_opts + 2 :]
|
160
|
-
return options
|
161
|
-
|
162
|
-
|
163
|
-
def get_memory_usage(ru: resource.struct_rusage) -> int:
|
164
|
-
if sys.platform == 'darwin':
|
165
|
-
return ru.ru_maxrss // 1024 + ru.ru_ixrss
|
166
|
-
return ru.ru_maxrss + ru.ru_ixrss + ru.ru_idrss + ru.ru_isrss
|
167
|
-
|
168
|
-
|
169
|
-
def get_cpu_time(ru: resource.struct_rusage) -> float:
|
170
|
-
return ru.ru_utime + ru.ru_stime
|
171
|
-
|
172
|
-
|
173
|
-
def _get_file_size(filename: Optional[str]) -> int:
|
174
|
-
if filename is None:
|
175
|
-
return 0
|
176
|
-
path = pathlib.Path(filename)
|
177
|
-
if not path.is_file():
|
178
|
-
return 0
|
179
|
-
return path.stat().st_size
|
180
|
-
|
181
|
-
|
182
|
-
def get_file_sizes(options: Options):
|
183
|
-
return _get_file_size(options.stdout_file) + _get_file_size(options.stderr_file)
|
184
|
-
|
185
|
-
|
186
|
-
def set_rlimits(options: Options):
|
187
|
-
if options.time_limit is not None:
|
188
|
-
time_limit_in_ms = int(options.time_limit * 1000)
|
189
|
-
rlimit_cpu = int((time_limit_in_ms + 999) // 1000)
|
190
|
-
resource.setrlimit(resource.RLIMIT_CPU, (rlimit_cpu, rlimit_cpu + 1))
|
191
|
-
if options.fs_limit is not None:
|
192
|
-
fs_limit = options.fs_limit * 1024 # in bytes
|
193
|
-
resource.setrlimit(resource.RLIMIT_FSIZE, (fs_limit + 1, fs_limit * 2))
|
194
|
-
|
195
|
-
|
196
|
-
def redirect_fds(options: Options):
|
197
|
-
files = [options.stdin_file, options.stdout_file, options.stderr_file]
|
198
|
-
|
199
|
-
for i in options.files_to_open:
|
200
|
-
file = files[i]
|
201
|
-
if file is None:
|
202
|
-
continue
|
203
|
-
open_args = [
|
204
|
-
os.O_WRONLY | os.O_TRUNC | os.O_CREAT,
|
205
|
-
stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR,
|
206
|
-
]
|
207
|
-
if i == 0:
|
208
|
-
# stdin
|
209
|
-
open_args = [os.O_RDONLY]
|
210
|
-
if i in options.file_duplicates:
|
211
|
-
dups = [
|
212
|
-
Tee(f, prefix=options.prefix if f in options.prefixed else '')
|
213
|
-
for f in options.file_duplicates[i]
|
214
|
-
]
|
215
|
-
tee = create_tee(dups + [file], 'a', prefix=options.prefix)
|
216
|
-
fd = tee.fileno()
|
217
|
-
else:
|
218
|
-
fd = os.open(
|
219
|
-
file,
|
220
|
-
*open_args,
|
221
|
-
)
|
222
|
-
os.dup2(fd, i)
|
223
|
-
os.close(fd)
|
224
|
-
|
225
|
-
|
226
|
-
def wait_and_finish(
|
227
|
-
pid: int,
|
228
|
-
options: Options,
|
229
|
-
start_time: float,
|
230
|
-
status_holder: Set[str],
|
231
|
-
alarm_msg: Optional[List[Optional[str]]] = None,
|
232
|
-
):
|
233
|
-
_, exitstatus, ru = os.wait4(pid, 0)
|
234
|
-
wall_time = monotonic() - start_time
|
235
|
-
cpu_time = get_cpu_time(ru)
|
236
|
-
memory_used = get_memory_usage(ru)
|
237
|
-
file_sizes = get_file_sizes(options)
|
238
|
-
|
239
|
-
entries = []
|
240
|
-
exitcode = os.waitstatus_to_exitcode(exitstatus)
|
241
|
-
entries.append(f'exit-code: {exitcode}')
|
242
|
-
if exitcode < 0:
|
243
|
-
entries.append(f'exit-sig: {-exitcode}')
|
244
|
-
|
245
|
-
status = status_holder
|
246
|
-
if exitcode > 0:
|
247
|
-
status.add('RE')
|
248
|
-
if exitcode < 0:
|
249
|
-
status.add('SG')
|
250
|
-
if options.time_limit is not None and (
|
251
|
-
cpu_time > options.time_limit or -exitcode == 24
|
252
|
-
):
|
253
|
-
status.add('TO')
|
254
|
-
cpu_time = max(cpu_time, options.time_limit)
|
255
|
-
if options.wall_time_limit is not None and wall_time > options.wall_time_limit:
|
256
|
-
status.add('WT')
|
257
|
-
status.add('TO')
|
258
|
-
if options.memory_limit is not None and memory_used > options.memory_limit:
|
259
|
-
status.add('ML')
|
260
|
-
if options.fs_limit is not None and file_sizes > options.fs_limit * 1024:
|
261
|
-
status.add('OL')
|
262
|
-
|
263
|
-
if status:
|
264
|
-
status_str = ','.join(status)
|
265
|
-
entries.append(f'status: {status_str}')
|
266
|
-
|
267
|
-
if alarm_msg:
|
268
|
-
alarm_str = ','.join(msg for msg in alarm_msg if msg is not None)
|
269
|
-
if alarm_str:
|
270
|
-
entries.append(f'alarm-msg: {alarm_str}')
|
271
|
-
|
272
|
-
entries.append(f'time: {cpu_time:.3f}')
|
273
|
-
entries.append(f'time-wall: {wall_time:.3f}')
|
274
|
-
entries.append(f'mem: {memory_used}')
|
275
|
-
entries.append(f'file: {file_sizes}')
|
276
|
-
entries.append(f'pid: {pid}')
|
277
|
-
|
278
|
-
output_file = pathlib.Path(sys.argv[1])
|
279
|
-
output_file.parent.mkdir(parents=True, exist_ok=True)
|
280
|
-
output_file.write_text('\n'.join(entries) + '\n')
|
281
|
-
|
282
|
-
|
283
|
-
def main():
|
284
|
-
options = parse_opts()
|
285
|
-
|
286
|
-
if options.process_group is not None:
|
287
|
-
os.setpgid(0, options.process_group)
|
288
|
-
|
289
|
-
start_time = monotonic()
|
290
|
-
sub_pid = os.fork()
|
291
|
-
if sub_pid == 0:
|
292
|
-
if options.chdir is not None:
|
293
|
-
os.chdir(options.chdir)
|
294
|
-
set_rlimits(options)
|
295
|
-
redirect_fds(options)
|
296
|
-
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
297
|
-
os.execvp(options.argv[0], options.argv)
|
298
|
-
|
299
|
-
alarm_msg: List[Optional[str]] = [None]
|
300
|
-
status_holder: Set[str] = set()
|
301
|
-
|
302
|
-
stop_wall_handler = threading.Event()
|
303
|
-
stop_alarm_handler = threading.Event()
|
304
|
-
|
305
|
-
def handle_wall():
|
306
|
-
if stop_wall_handler.wait(options.wall_time_limit):
|
307
|
-
return
|
308
|
-
stop_alarm_handler.set()
|
309
|
-
nonlocal alarm_msg
|
310
|
-
alarm_msg[0] = 'wall timelimit'
|
311
|
-
os.kill(sub_pid, 9)
|
312
|
-
|
313
|
-
def handle_alarm():
|
314
|
-
if stop_alarm_handler.wait(0.3):
|
315
|
-
return
|
316
|
-
nonlocal alarm_msg
|
317
|
-
ru = resource.getrusage(resource.RUSAGE_CHILDREN)
|
318
|
-
if options.time_limit is not None:
|
319
|
-
cpu_time = get_cpu_time(ru)
|
320
|
-
if cpu_time > options.time_limit:
|
321
|
-
alarm_msg[0] = 'timelimit'
|
322
|
-
os.kill(sub_pid, 9)
|
323
|
-
return
|
324
|
-
if options.memory_limit is not None:
|
325
|
-
memory_used = get_memory_usage(ru)
|
326
|
-
if memory_used > options.memory_limit:
|
327
|
-
alarm_msg[0] = 'memorylimit'
|
328
|
-
os.kill(sub_pid, 9)
|
329
|
-
return
|
330
|
-
|
331
|
-
stop_alarm_handler.clear()
|
332
|
-
handle_alarm()
|
333
|
-
|
334
|
-
alarm_handler = threading.Thread(target=handle_alarm, daemon=True)
|
335
|
-
wall_handler = threading.Thread(target=handle_wall, daemon=True)
|
336
|
-
alarm_handler.start()
|
337
|
-
wall_handler.start()
|
338
|
-
|
339
|
-
def handle_sub_term(*args, **kwargs):
|
340
|
-
nonlocal status_holder
|
341
|
-
status_holder.add('TE')
|
342
|
-
os.kill(sub_pid, 9)
|
343
|
-
|
344
|
-
signal.signal(signal.SIGTERM, handle_sub_term)
|
345
|
-
|
346
|
-
wait_and_finish(sub_pid, options, start_time, status_holder, alarm_msg=alarm_msg)
|
347
|
-
|
348
|
-
# Process finished, stop the handlers.
|
349
|
-
stop_wall_handler.set()
|
350
|
-
stop_alarm_handler.set()
|
351
|
-
|
352
|
-
# Exit gracefully.
|
353
|
-
sys.exit(0)
|
354
|
-
|
355
|
-
|
356
|
-
if __name__ == '__main__':
|
357
|
-
main()
|
358
|
-
# type: ignore
|
rbx/grading/judge/test.py
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
import atexit
|
2
|
-
import pathlib
|
3
|
-
|
4
|
-
from rich.console import Console
|
5
|
-
|
6
|
-
from rbx.grading.judge import cacher, storage
|
7
|
-
from rbx.grading.judge.sandboxes import stupid_sandbox
|
8
|
-
|
9
|
-
console = Console()
|
10
|
-
|
11
|
-
|
12
|
-
def main():
|
13
|
-
fs = storage.FilesystemStorage(pathlib.PosixPath('/tmp/rbx-storage'))
|
14
|
-
cache = cacher.FileCacher(fs)
|
15
|
-
|
16
|
-
python_file = cache.put_file_text("print('hello')")
|
17
|
-
|
18
|
-
sandbox = stupid_sandbox.StupidSandbox(cache)
|
19
|
-
atexit.register(sandbox.cleanup)
|
20
|
-
sandbox.create_file_from_storage(pathlib.PosixPath('run.py'), python_file)
|
21
|
-
|
22
|
-
sandbox.params.stdout_file = pathlib.PosixPath('run.out')
|
23
|
-
|
24
|
-
sandbox.execute_without_std(['ls'])
|
25
|
-
try:
|
26
|
-
sandbox.hydrate_logs()
|
27
|
-
except Exception:
|
28
|
-
console.print_exception()
|
29
|
-
|
30
|
-
print(sandbox.get_human_exit_description())
|
31
|
-
print(sandbox.get_stats())
|
32
|
-
print(sandbox.log)
|
33
|
-
|
34
|
-
print(sandbox.get_file_to_string(pathlib.PosixPath('run.out')))
|
35
|
-
|
36
|
-
|
37
|
-
if __name__ == '__main__':
|
38
|
-
main()
|
rbx/grading/judge/testiso.py
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
import atexit
|
2
|
-
import pathlib
|
3
|
-
|
4
|
-
from rich.console import Console
|
5
|
-
|
6
|
-
from rbx import grading_utils
|
7
|
-
from rbx.grading.judge import cacher, storage
|
8
|
-
from rbx.grading.judge.sandboxes.isolate import IsolateSandbox
|
9
|
-
|
10
|
-
console = Console()
|
11
|
-
|
12
|
-
|
13
|
-
def main():
|
14
|
-
fs = storage.FilesystemStorage(pathlib.PosixPath('/tmp/rbx-storage'))
|
15
|
-
cache = cacher.FileCacher(fs)
|
16
|
-
|
17
|
-
python_file = cache.put_file_text(
|
18
|
-
"""
|
19
|
-
#include <bits/stdc++.h>
|
20
|
-
|
21
|
-
int main() {
|
22
|
-
std::cout << "Hello, World!" << std::endl;
|
23
|
-
return 0;
|
24
|
-
}
|
25
|
-
"""
|
26
|
-
)
|
27
|
-
|
28
|
-
sandbox = IsolateSandbox(
|
29
|
-
cache, params=grading_utils.build_preprocess_sandbox_params(), debug=True
|
30
|
-
)
|
31
|
-
atexit.register(sandbox.cleanup)
|
32
|
-
sandbox.create_file_from_storage(pathlib.PosixPath('run.cpp'), python_file)
|
33
|
-
|
34
|
-
sandbox.params.stdout_file = pathlib.PosixPath('run.out')
|
35
|
-
sandbox.params.stderr_file = pathlib.PosixPath('run.err')
|
36
|
-
|
37
|
-
sandbox.execute_without_std(
|
38
|
-
['/usr/bin/g++', '-std=c++17', '-o', 'executable', 'run.cpp'],
|
39
|
-
)
|
40
|
-
try:
|
41
|
-
sandbox.hydrate_logs()
|
42
|
-
except Exception:
|
43
|
-
console.print_exception()
|
44
|
-
|
45
|
-
print(sandbox.log)
|
46
|
-
print(sandbox.get_human_exit_description())
|
47
|
-
print(sandbox.get_stats())
|
48
|
-
|
49
|
-
print(sandbox.get_file_to_string(pathlib.PosixPath('run.out')))
|
50
|
-
print(sandbox.get_file_to_string(pathlib.PosixPath('run.err')))
|
51
|
-
|
52
|
-
|
53
|
-
if __name__ == '__main__':
|
54
|
-
main()
|
@@ -1,71 +0,0 @@
|
|
1
|
-
import contextlib
|
2
|
-
import os
|
3
|
-
import signal
|
4
|
-
import subprocess
|
5
|
-
from typing import List, Optional
|
6
|
-
|
7
|
-
from rbx.grading.judge.sandbox import SandboxBase
|
8
|
-
|
9
|
-
|
10
|
-
@contextlib.contextmanager
|
11
|
-
def new_process_group():
|
12
|
-
p = subprocess.Popen(['/bin/bash', '-c', 'exec sleep infinity'])
|
13
|
-
try:
|
14
|
-
yield p.pid
|
15
|
-
finally:
|
16
|
-
p.terminate()
|
17
|
-
p.wait()
|
18
|
-
|
19
|
-
|
20
|
-
def should_use_group(sandboxes: List[SandboxBase]) -> bool:
|
21
|
-
if not sandboxes:
|
22
|
-
return False
|
23
|
-
uses_pgid = all(sandbox.use_pgid() for sandbox in sandboxes)
|
24
|
-
all_pgids = set(
|
25
|
-
sandbox.params.pgid for sandbox in sandboxes if sandbox.params.pgid is not None
|
26
|
-
)
|
27
|
-
return uses_pgid and len(all_pgids) == 1
|
28
|
-
|
29
|
-
|
30
|
-
async def _fetch_pids(sandboxes: List[SandboxBase]) -> List[int]:
|
31
|
-
return [await sandbox.get_pid() for sandbox in sandboxes]
|
32
|
-
|
33
|
-
|
34
|
-
def _find_sandbox_idx(pids: List[int], pid: int) -> Optional[int]:
|
35
|
-
try:
|
36
|
-
return pids.index(pid)
|
37
|
-
except ValueError:
|
38
|
-
return None
|
39
|
-
|
40
|
-
|
41
|
-
async def _wait_for_group(sandboxes: List[SandboxBase]) -> List[int]:
|
42
|
-
pgid = [
|
43
|
-
sandbox.params.pgid for sandbox in sandboxes if sandbox.params.pgid is not None
|
44
|
-
][0]
|
45
|
-
assert pgid is not None
|
46
|
-
|
47
|
-
sandbox_pids = await _fetch_pids(sandboxes)
|
48
|
-
|
49
|
-
finished = []
|
50
|
-
while len(finished) < len(sandboxes):
|
51
|
-
try:
|
52
|
-
pid, status = os.waitpid(-pgid, 0)
|
53
|
-
except ChildProcessError:
|
54
|
-
break
|
55
|
-
|
56
|
-
if os.waitstatus_to_exitcode(status) != 0:
|
57
|
-
os.kill(pgid, signal.SIGKILL)
|
58
|
-
|
59
|
-
sandbox_idx = _find_sandbox_idx(sandbox_pids, pid)
|
60
|
-
if sandbox_idx is not None:
|
61
|
-
finished.append(sandbox_idx)
|
62
|
-
continue
|
63
|
-
|
64
|
-
return finished
|
65
|
-
|
66
|
-
|
67
|
-
async def wait_all(sandboxes: List[SandboxBase]):
|
68
|
-
if not should_use_group(sandboxes):
|
69
|
-
raise RuntimeError('Sandboxes are not using a process group')
|
70
|
-
|
71
|
-
await _wait_for_group(sandboxes)
|
@@ -1,36 +0,0 @@
|
|
1
|
-
---
|
2
|
-
sandbox: "isolate"
|
3
|
-
defaultCompilation:
|
4
|
-
sandbox:
|
5
|
-
maxProcesses: null
|
6
|
-
timeLimit: null # 10 seconds
|
7
|
-
wallTimeLimit: null # 10 seconds
|
8
|
-
memoryLimit: null # 1gb
|
9
|
-
preserveEnv: true
|
10
|
-
mirrorDirs:
|
11
|
-
- "/etc"
|
12
|
-
- "/usr"
|
13
|
-
defaultExecution:
|
14
|
-
sandbox:
|
15
|
-
# Useful for checkers, validators, etc.
|
16
|
-
timeLimit: 10000 # 10 seconds
|
17
|
-
wallTimeLimit: 10000 # 10 seconds
|
18
|
-
memoryLimit: 1024 # 1gb
|
19
|
-
languages:
|
20
|
-
- name: "cpp"
|
21
|
-
readableName: "C++17"
|
22
|
-
extension: "cpp"
|
23
|
-
compilation:
|
24
|
-
commands:
|
25
|
-
- "/usr/bin/g++ -std=c++17 -O2 -o {executable} {compilable}"
|
26
|
-
execution:
|
27
|
-
command: "./{executable}"
|
28
|
-
fileMapping:
|
29
|
-
compilable: "compilable.cpp"
|
30
|
-
- name: "py"
|
31
|
-
readableName: "Python3"
|
32
|
-
extension: "py"
|
33
|
-
execution:
|
34
|
-
command: "/usr/bin/python3 {executable}"
|
35
|
-
fileMapping:
|
36
|
-
executable: "executable.py"
|
File without changes
|
File without changes
|
File without changes
|