From 453434c2b2f2f070fbc9f40aed4cbe517316cd45 Mon Sep 17 00:00:00 2001 From: KeelrLeek <156356974+KeelrLeek@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:29:36 -0500 Subject: [PATCH] re_match_program: one whole-program build per worktree at a time (a lock file); concurrent runs collided --- harbor/reverse.py | 55 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/harbor/reverse.py b/harbor/reverse.py index 72bee46..3cbacbe 100644 --- a/harbor/reverse.py +++ b/harbor/reverse.py @@ -598,15 +598,19 @@ def match_program(config, targets=None, decomp=None): text=True, encoding="utf-8", errors="replace", timeout=timeout, cwd=str(Path(script).parent), creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0)) - built = run(["build", *extra], 1800) - if built.returncode != 0: - raise HarborError("the whole-program build failed: " + (built.stdout + built.stderr).strip()[-800:]) - with tempfile.TemporaryDirectory(dir=config.get("debugger_temp")) as tmp: - out = Path(tmp) / "check.json" - checked = run(["check", *extra, "--json", str(out)] + (["--only", ",".join(wanted)] if wanted else []), 900) - if not out.exists(): - raise HarborError("the whole-program check failed: " + (checked.stdout + checked.stderr).strip()[-800:]) - rows = json.loads(out.read_text(encoding="utf-8")) + # one build per worktree at a time: two runs on the same checkout write the same build folder + lock_dir = Path(config.get("debugger_temp") or tempfile.gettempdir()) + lock = lock_dir / ("ltcg-" + re.sub(r"[^A-Za-z0-9]+", "_", str(decomp or "main")) + ".lock") + with _exclusive(lock, wait_seconds=1800): + built = run(["build", *extra], 1800) + if built.returncode != 0: + raise HarborError("the whole-program build failed: " + (built.stdout + built.stderr).strip()[-800:]) + with tempfile.TemporaryDirectory(dir=config.get("debugger_temp")) as tmp: + out = Path(tmp) / "check.json" + checked = run(["check", *extra, "--json", str(out)] + (["--only", ",".join(wanted)] if wanted else []), 900) + if not out.exists(): + raise HarborError("the whole-program check failed: " + (checked.stdout + checked.stderr).strip()[-800:]) + rows = json.loads(out.read_text(encoding="utf-8")) counts = {} for r in rows: counts[r.get("status")] = counts.get(r.get("status"), 0) + 1 @@ -642,3 +646,36 @@ def behaviour_check(config, targets, decomp=None, precision=24): rows = json.loads(out.read_text(encoding="utf-8")) keep = ("address", "name", "verdict", "reason", "weak", "counts", "notes") return [{k: r[k] for k in keep if k in r} for r in rows] + + +class _exclusive: + """A lock file shared between processes: wait for it, hold it, remove it (stale after two hours).""" + + def __init__(self, path, wait_seconds=1800): + self.path, self.wait = Path(path), wait_seconds + + def __enter__(self): + import time + deadline = time.monotonic() + self.wait + while True: + try: + fd = os.open(str(self.path), os.O_CREAT | os.O_EXCL | os.O_WRONLY) + os.write(fd, str(os.getpid()).encode()) + os.close(fd) + return self + except FileExistsError: + try: + if time.time() - self.path.stat().st_mtime > 7200: + self.path.unlink() + continue + except FileNotFoundError: + continue + if time.monotonic() > deadline: + raise HarborError(f"another whole-program build on this worktree is still running ({self.path.name}); try again later") from None + time.sleep(5) + + def __exit__(self, *exc): + try: + self.path.unlink() + except FileNotFoundError: + pass