rbx.cp 0.5.53__py3-none-any.whl → 0.5.54__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 CHANGED
@@ -226,6 +226,10 @@ def _check_sanitizer_warnings(run_log: Optional[RunLog]) -> bool:
226
226
  return run_log.warnings
227
227
 
228
228
 
229
+ def _is_testlib_eof(stderr: str) -> bool:
230
+ return 'wrong output format Unexpected end of file' in stderr
231
+
232
+
229
233
  async def check(
230
234
  checker_digest: str,
231
235
  run_log: Optional[RunLog],
@@ -283,15 +287,21 @@ async def check_communication(
283
287
  # interactor exited before it. Thus, check the interactor, as it might have
284
288
  # returned a checker verdict.
285
289
  #
286
- # Also, do the same if the solution returned a non-zero exit code. Checker verdict
287
- # should usually supersede a solution's RTE verdict.
290
+ # Also, treat the case where the solution has a non-zero exit code similarly.
291
+ # This is aligned with what Polygon/Codeforces does: for some languages, we don't
292
+ # get a SIGPIPE. Instead, we get a non-zero exit code which we can't distinguish
293
+ # from a normal RTE. Thus, we decide that we should prioritize the interactor verdict
294
+ # over the solution's exit code in these cases.
288
295
  if (
289
296
  interactor_run_log is not None
290
297
  and run_log is not None
291
298
  and (
292
299
  run_log.exitcode == -signal.SIGPIPE
293
300
  or run_log.exitstatus == SandboxBase.EXIT_TERMINATED
294
- or run_log.exitstatus == SandboxBase.EXIT_NONZERO_RETURN
301
+ or (
302
+ run_log.exitstatus == SandboxBase.EXIT_NONZERO_RETURN
303
+ and not _is_testlib_eof(interactor_stderr.read_text())
304
+ )
295
305
  )
296
306
  ):
297
307
  result = _check_interactor()
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import contextlib
2
3
  import os
3
4
  import signal
@@ -5,22 +6,26 @@ import threading
5
6
  from typing import Optional, Set
6
7
 
7
8
  _processing_context_pids: Optional[Set[int]] = None
9
+ _terminate_all_on_error = False
8
10
  _lock = threading.Lock()
9
11
 
10
12
  # Creating a processing context is not thread-safe, but adding to it is.
11
13
 
12
14
 
13
15
  @contextlib.contextmanager
14
- def new_processing_context():
15
- global _processing_context_pids
16
+ def new_processing_context(terminate_all_on_error: bool = False):
17
+ global _processing_context_pids, _terminate_all_on_error
16
18
  with _lock:
17
19
  old_processing_context_pids = _processing_context_pids
20
+ _old_terminate_all_on_error = _terminate_all_on_error
18
21
  _processing_context_pids = set()
22
+ _terminate_all_on_error = terminate_all_on_error
19
23
  try:
20
24
  yield
21
25
  finally:
22
26
  with _lock:
23
27
  _processing_context_pids = old_processing_context_pids
28
+ _terminate_all_on_error = _old_terminate_all_on_error
24
29
 
25
30
 
26
31
  def get_processing_context() -> Set[int]:
@@ -36,7 +41,8 @@ def add_to_processing_context(pid: int):
36
41
  _processing_context_pids.add(pid)
37
42
 
38
43
 
39
- def terminate_all_processes_in_context():
44
+ def terminate_all_processes_in_context(clear: bool = True):
45
+ global _processing_context_pids
40
46
  with _lock:
41
47
  if _processing_context_pids is None:
42
48
  return
@@ -45,4 +51,37 @@ def terminate_all_processes_in_context():
45
51
  os.kill(pid, signal.SIGTERM)
46
52
  except OSError:
47
53
  pass
48
- _processing_context_pids.clear()
54
+ if clear:
55
+ _processing_context_pids.clear()
56
+
57
+
58
+ async def wait_all_processes_in_context(wait_for: int):
59
+ global _processing_context_pids, _terminate_all_on_error
60
+ wait_pids = set()
61
+ while len(get_processing_context()) < wait_for:
62
+ await asyncio.sleep(0.01)
63
+
64
+ with _lock:
65
+ if _processing_context_pids is None:
66
+ return
67
+ wait_pids.update(_processing_context_pids)
68
+
69
+ wait_lock = threading.Lock()
70
+ finished_pids = []
71
+
72
+ def process(pid: int, returncode: int):
73
+ with wait_lock:
74
+ finished_pids.append(pid)
75
+ if returncode != 0 and _terminate_all_on_error:
76
+ terminate_all_processes_in_context()
77
+
78
+ def wait_all_processes():
79
+ while len(finished_pids) < len(wait_pids):
80
+ try:
81
+ pid, status = os.wait()
82
+ except ChildProcessError:
83
+ return
84
+ if pid in wait_pids:
85
+ process(pid, os.waitstatus_to_exitcode(status))
86
+
87
+ await asyncio.to_thread(wait_all_processes)
rbx/grading/steps.py CHANGED
@@ -193,6 +193,11 @@ class RunLogMetadata(BaseModel):
193
193
  retryIndex: Optional[int] = None
194
194
 
195
195
 
196
+ class ProcessingContextLog(BaseModel):
197
+ pid: int = -1
198
+ exitindex: int = -1
199
+
200
+
196
201
  class RunLog(BaseModel):
197
202
  exitcode: int = 0
198
203
  exitstatus: str = SandboxBase.EXIT_SANDBOX_ERROR
@@ -330,14 +335,29 @@ def _expand_part(part: str, sandbox: SandboxBase) -> List[str]:
330
335
  return [part]
331
336
 
332
337
 
338
+ def _get_java_memory_limits(sandbox: SandboxBase) -> Tuple[int, int]:
339
+ max_memory = sandbox.params.address_space
340
+ if max_memory is None:
341
+ max_memory = 2048
342
+ return max_memory, min(512, int(max_memory * 0.9))
343
+
344
+
333
345
  def _split_and_expand(command: str, sandbox: SandboxBase) -> List[str]:
334
346
  res = []
335
- parts = shlex.split(command.format(memory=sandbox.params.address_space or 2048))
347
+ max_mem, init_mem = _get_java_memory_limits(sandbox)
348
+ parts = shlex.split(command.format(memory=max_mem, initialMemory=init_mem))
336
349
  for part in parts:
337
350
  res.extend(_expand_part(part, sandbox))
338
351
  return res
339
352
 
340
353
 
354
+ def get_exe_from_command(command: str) -> str:
355
+ cmds = shlex.split(command)
356
+ if not cmds:
357
+ return command
358
+ return cmds[0]
359
+
360
+
341
361
  def _is_c_command(exe_command: str) -> bool:
342
362
  return 'gcc' in exe_command or 'clang' in exe_command
343
363
 
@@ -351,15 +371,26 @@ def is_cxx_command(exe_command: str) -> bool:
351
371
 
352
372
 
353
373
  def is_cxx_sanitizer_command(command: str) -> bool:
354
- cmds = shlex.split(command)
355
- if not cmds:
374
+ exe = get_exe_from_command(command)
375
+ if not exe:
356
376
  return False
357
- exe = cmds[0]
358
377
  if not is_cxx_command(exe):
359
378
  return False
360
379
  return 'fsanitize' in command
361
380
 
362
381
 
382
+ def is_java_command(exe_command: str) -> bool:
383
+ return 'javac' in exe_command or 'java' in exe_command
384
+
385
+
386
+ def is_kotlin_command(exe_command: str) -> bool:
387
+ return 'kotlinc' in exe_command or 'kotlin' in exe_command
388
+
389
+
390
+ def is_java_like_command(exe_command: str) -> bool:
391
+ return is_java_command(exe_command) or is_kotlin_command(exe_command)
392
+
393
+
363
394
  @functools.cache
364
395
  def _complain_about_clang() -> None:
365
396
  console.print(
@@ -539,6 +570,10 @@ def compile(
539
570
  stderr_file = pathlib.PosixPath(f'compile-{i}.stderr')
540
571
  sandbox.params.set_stdall(stdout=stdout_file, stderr=stderr_file)
541
572
 
573
+ # Remove memory constraints for Java.
574
+ if is_java_like_command(get_exe_from_command(command)):
575
+ sandbox.params.address_space = None
576
+
542
577
  if bits_artifact is not None and _is_cpp_command(cmd[0]):
543
578
  # Include from sandbox directory to import bits/stdc++.h.
544
579
  cmd.append('-I.')
@@ -604,6 +639,10 @@ async def run(
604
639
  cmd = _split_and_expand(command, sandbox)
605
640
  sandbox.set_params(params)
606
641
 
642
+ # Remove memory constraints for Java.
643
+ if is_java_like_command(get_exe_from_command(command)):
644
+ sandbox.params.address_space = None
645
+
607
646
  if not await asyncio.to_thread(sandbox.execute_without_std, cmd):
608
647
  console.print(
609
648
  '[error]Sandbox crashed while processing command:[/error]',
@@ -614,7 +653,7 @@ async def run(
614
653
  return None
615
654
 
616
655
  if sandbox.get_exit_code() != 0 and kill_on_processing_context_exit:
617
- processing_context.terminate_all_processes_in_context()
656
+ processing_context.terminate_all_processes_in_context(clear=False)
618
657
 
619
658
  if not _process_output_artifacts(artifacts, sandbox):
620
659
  return None
@@ -657,22 +696,27 @@ async def run_coordinated(
657
696
  interactor: CoordinatedRunParams,
658
697
  solution: CoordinatedRunParams,
659
698
  ) -> Tuple[Optional[RunLog], Optional[RunLog]]:
660
- with processing_context.new_processing_context():
699
+ with processing_context.new_processing_context(terminate_all_on_error=True):
700
+ # Schedule both runs to execute immediately.
661
701
  runs = tuple(
662
- run(
663
- params.command,
664
- params.params,
665
- params.sandbox,
666
- params.artifacts,
667
- params.metadata,
668
- kill_on_processing_context_exit=True,
702
+ asyncio.create_task(
703
+ run(
704
+ params.command,
705
+ params.params,
706
+ params.sandbox,
707
+ params.artifacts,
708
+ params.metadata,
709
+ kill_on_processing_context_exit=True,
710
+ )
669
711
  )
670
712
  for params in [interactor, solution]
671
713
  )
672
- return typing.cast(
714
+ await processing_context.wait_all_processes_in_context(wait_for=2)
715
+ logs = typing.cast(
673
716
  Tuple[Optional[RunLog], Optional[RunLog]],
674
717
  tuple(await asyncio.gather(*runs)),
675
718
  )
719
+ return logs
676
720
 
677
721
 
678
722
  def _normalize_checked_words(s: str) -> Tuple[str, ...]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rbx.cp
3
- Version: 0.5.53
3
+ Version: 0.5.54
4
4
  Summary:
5
5
  Author: Roberto Sales
6
6
  Requires-Python: >=3.9,<4.0
@@ -21,6 +21,7 @@ Requires-Dist: latexbuild (>=0.2.2,<0.3.0)
21
21
  Requires-Dist: mechanize (>=0.4.10,<0.5.0)
22
22
  Requires-Dist: more-itertools (>=10.5.0,<11.0.0)
23
23
  Requires-Dist: nest-asyncio (>=1.6.0,<2.0.0)
24
+ Requires-Dist: psutil (>=7.0.0,<8.0.0)
24
25
  Requires-Dist: pydantic (==2.8.2)
25
26
  Requires-Dist: pydantic-xml[lxml] (>=2.11.0,<3.0.0)
26
27
  Requires-Dist: pyte (>=0.8.2,<0.9.0)
@@ -4,7 +4,7 @@ rbx/autoenum.py,sha256=cusv8ClXRlDVvhZ8eDrtYcL_2peXlHugAey_ht8roXk,12025
4
4
  rbx/box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  rbx/box/builder.py,sha256=MDm2qqmhedAbhn3rWP6cDwbBsGhV6sz_2sg1zLkPDw0,3613
6
6
  rbx/box/cd.py,sha256=9a_SOnzoJBXxxffp4Wbf3UKXIwKuN3Hvj7K6SocALwE,1194
7
- rbx/box/checkers.py,sha256=eb4tqtVUJYlme_Vkj2TGkUABM7EMS0P1EswhMYjN7BI,11459
7
+ rbx/box/checkers.py,sha256=5Pj6a8y5YCVY3KvV7B_WLo30uDmIhnEgxE6_KKZqzvY,11923
8
8
  rbx/box/cli.py,sha256=GPoGDgmaV1lXL9puXhUbZUIEU0lOMvdH1kD4TGCXkCo,26738
9
9
  rbx/box/code.py,sha256=hmA2EoGOr13AYzicHNsnU2SkFW-44l7kOdu8QRwTJlI,18848
10
10
  rbx/box/compile.py,sha256=OJLthDQ921w9vyoE6Gk1Df54i5RwtRJ2YG-8XEfefcs,2489
@@ -98,8 +98,8 @@ rbx/grading/judge/sandboxes/timeit.py,sha256=jQonQhaVI93diYQsJYXwS77IO1t5iZVKJtm
98
98
  rbx/grading/judge/storage.py,sha256=3vv0HvtenbUZBH33CB5ZzX66ppL22G6munBaAA9BgwQ,9418
99
99
  rbx/grading/judge/test.py,sha256=ll0Iw7zyOpGdKPD_PGH7dvUkb4stQLu-ikbQnqJvuAc,944
100
100
  rbx/grading/judge/testiso.py,sha256=v14DtkWiZFJ9AKMzrb0_vZKPWDt8jz8iIw1Z2O-Advk,1397
101
- rbx/grading/processing_context.py,sha256=2fxa610WzXGvICDVWkPCG721w-1tXDVT7w_KtSnD0OM,1213
102
- rbx/grading/steps.py,sha256=RpA6HjQ4NaZLMryyFFGBW53QojLoD9VH5yjuZMycWds,25752
101
+ rbx/grading/processing_context.py,sha256=EOxsRTKB_JEgcKNodDWPIYaBramANU-6QnDkqdF8tEk,2556
102
+ rbx/grading/steps.py,sha256=VscFO-fnKtSspdB1Cnu18l8feaREbsh0MNkexJ6obOc,27163
103
103
  rbx/grading/steps_with_caching.py,sha256=nez2YwgauGXKRjhk6tQxTDGQ-HEk7KfZOeAPhsxi5iw,3150
104
104
  rbx/grading/steps_with_caching_run_test.py,sha256=mh4DRInrOGhnQFWD1SlcjDm_HvcSDFTDMSpAlG-Q5SI,15570
105
105
  rbx/grading_utils.py,sha256=lL2KtSkOsMElqrRoApQTbFcqVOeHVWUDTMCa3IsLpC4,4484
@@ -193,8 +193,8 @@ rbx/testcase.py,sha256=yKOq3CAJZ1YTmInvnoIs0u1iJnRj_X85XiWbLI-p9d8,1951
193
193
  rbx/testcase_rendering.py,sha256=nfmv6dSEqd4aR3TsaODwkKGK6AXty_DDKtWf_ejiQpI,2084
194
194
  rbx/testing_utils.py,sha256=x_PqD8Zd2PkN91NxVHUnSTs044-1WK5KKtttKQBXpFs,2083
195
195
  rbx/utils.py,sha256=SfR844_i0ebRDMkmS_w1YdZiWPc6h2RGADygewlWRbA,4845
196
- rbx_cp-0.5.53.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
197
- rbx_cp-0.5.53.dist-info/METADATA,sha256=Gy6mjUs1yQKV0576ZRyBp5g3mjvxbU2L4t2xFf1u39s,3348
198
- rbx_cp-0.5.53.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
199
- rbx_cp-0.5.53.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
200
- rbx_cp-0.5.53.dist-info/RECORD,,
196
+ rbx_cp-0.5.54.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
197
+ rbx_cp-0.5.54.dist-info/METADATA,sha256=Ki-VbHPb5TES3dtzJ_muEqjIuzJRACCQ5MIhdIGG0G4,3387
198
+ rbx_cp-0.5.54.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
199
+ rbx_cp-0.5.54.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
200
+ rbx_cp-0.5.54.dist-info/RECORD,,