Spaces:
Running
Running
File size: 6,828 Bytes
67acd34 30865cf f227b96 96e3e70 67acd34 96e3e70 b266f7c 96e3e70 67acd34 b266f7c f227b96 30865cf f227b96 67acd34 b266f7c f227b96 96e3e70 f227b96 b266f7c 96e3e70 f227b96 b266f7c f227b96 96e3e70 f227b96 b266f7c 96e3e70 f227b96 96e3e70 f227b96 96e3e70 f227b96 96e3e70 30865cf 67acd34 30865cf f227b96 b266f7c 96e3e70 67acd34 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | from __future__ import annotations
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any
ROOT_DIR = Path(__file__).resolve().parents[1]
EVALS_SRC_DIR = ROOT_DIR / "evals" / "src"
if str(EVALS_SRC_DIR) not in sys.path:
sys.path.insert(0, str(EVALS_SRC_DIR))
from evals_verifier import ( # type: ignore
check_bridges_structural_validity,
check_galaxies_structural_validity,
check_undead_structural_validity,
verify_with_rlp,
)
from puzzles import PUZZLES # type: ignore
# Puzzle families whose "solved" check runs in the native RLP C library (loaded
# in-process via ctypes). That library can segfault or hang when its puzzle
# objects are reused across many requests in a long-lived server process, which
# crashes the web worker and surfaces as an opaque HTTP 500 (and, sometimes, a
# hung request). We run every native check in a short-lived subprocess so a
# native crash/hang kills only the child and we return a clean "not solved"
# result instead of taking down the worker. (flow_free already shells out to its
# own compiled solver binary, so it is naturally isolated.)
SUBPROCESS_RLP_PUZZLES = {"undead", "loopy", "bridges", "galaxies", "pattern"}
RLP_VERIFIER_TIMEOUT_SECONDS = 20
# flow_free shells out to a compiled C solver with no internal timeout and can
# raise SystemExit on a missing compiler/binary; isolate it too so it can never
# hang or crash the worker. Enumerating all solutions can be slower than the
# RLP solved-check, so it gets a more generous budget.
FLOW_FREE_TIMEOUT_SECONDS = 30
class VerificationService:
def _structural_validity(
self, *, puzzle_type: str, board_ascii: str, problem_ascii: str
) -> bool:
# Pure-Python structural pre-checks are safe to run in-process and also
# cover the "start board was modified" case for bridges/galaxies. Puzzle
# families without such a check are treated as structurally valid here;
# the subprocess solved-check is the source of truth for correctness.
if puzzle_type == "bridges":
return bool(
check_bridges_structural_validity(board_ascii, problem_ascii=problem_ascii)
)
if puzzle_type == "galaxies":
return bool(
check_galaxies_structural_validity(board_ascii, problem_ascii=problem_ascii)
)
if puzzle_type == "undead":
return bool(check_undead_structural_validity(board_ascii))
return True
def _verify_rlp_with_subprocess(
self,
*,
puzzle_type: str,
problem_ascii: str,
board_ascii: str,
args: str,
) -> dict[str, Any]:
result = {
"board_exists": bool(board_ascii),
"board_valid": False,
"board_modified": False,
"correct": False,
}
if not board_ascii:
return result
result["board_valid"] = self._structural_validity(
puzzle_type=puzzle_type, board_ascii=board_ascii, problem_ascii=problem_ascii
)
if not result["board_valid"]:
result["board_modified"] = True
return result
verifier_path = ROOT_DIR / "submodules" / "rlp" / "verifier.py"
command = [sys.executable, str(verifier_path), puzzle_type, "--arg", args]
# The RLP libraries pull in pygame, which noisily probes audio/video on
# import; force the dummy drivers (and silence warnings) so a headless
# verifier run does not pollute the captured stderr.
env = {
**os.environ,
"SDL_AUDIODRIVER": "dummy",
"SDL_VIDEODRIVER": "dummy",
"PYTHONWARNINGS": "ignore",
}
try:
completed = subprocess.run(
command,
input=board_ascii,
text=True,
capture_output=True,
cwd=ROOT_DIR,
timeout=RLP_VERIFIER_TIMEOUT_SECONDS,
check=False,
env=env,
)
except subprocess.TimeoutExpired:
result["error"] = f"{puzzle_type.capitalize()} verification timed out."
return result
result["correct"] = completed.returncode == 0 and "SOLVED" in completed.stdout
if completed.stderr.strip():
result["stderr"] = completed.stderr.strip()[:500]
return result
def _verify_flow_free_with_subprocess(
self, *, problem_ascii: str, board_ascii: str
) -> dict[str, Any]:
result = {
"board_exists": bool(board_ascii),
"board_valid": False,
"board_modified": False,
"correct": False,
}
if not board_ascii:
return result
runner_path = Path(__file__).resolve().parent / "flow_free_runner.py"
payload = json.dumps({"problem_ascii": problem_ascii, "board_ascii": board_ascii})
env = {**os.environ, "PYTHONWARNINGS": "ignore"}
try:
completed = subprocess.run(
[sys.executable, str(runner_path)],
input=payload,
text=True,
capture_output=True,
cwd=ROOT_DIR,
timeout=FLOW_FREE_TIMEOUT_SECONDS,
check=False,
env=env,
)
except subprocess.TimeoutExpired:
result["error"] = "Flow_free verification timed out."
return result
if completed.returncode == 0 and completed.stdout.strip():
try:
parsed = json.loads(completed.stdout)
if isinstance(parsed, dict):
return parsed
except json.JSONDecodeError:
pass
result["error"] = "Flow_free verification failed."
if completed.stderr.strip():
result["stderr"] = completed.stderr.strip()[:500]
return result
def verify(
self,
*,
puzzle_type: str,
problem_ascii: str,
board_ascii: str,
args: str,
) -> dict[str, Any]:
spec = PUZZLES[puzzle_type]
if spec.verifier_type == "flow_free":
return self._verify_flow_free_with_subprocess(
problem_ascii=problem_ascii, board_ascii=board_ascii
)
if spec.verifier_type in SUBPROCESS_RLP_PUZZLES:
return self._verify_rlp_with_subprocess(
puzzle_type=spec.verifier_type,
problem_ascii=problem_ascii,
board_ascii=board_ascii,
args=args,
)
return verify_with_rlp(
puzzle_type=spec.verifier_type,
problem_ascii=problem_ascii,
extracted_board=board_ascii,
args=args,
)
|