From 62184fe2d9a7907cdf1b557ab8b0ab3d7d6e6309 Mon Sep 17 00:00:00 2001 From: Cyberpradeep Date: Thu, 29 Oct 2026 22:37:54 +0530 Subject: [PATCH] fix(core): enable Windows support with msvcrt locking and POSIX tree keys (fixes #151) --- harness2/core/schedule.py | 31 +++++++++++++++++++++++++++---- harness2/core/tree.py | 4 ++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/harness2/core/schedule.py b/harness2/core/schedule.py index f91f450b..10b51839 100644 --- a/harness2/core/schedule.py +++ b/harness2/core/schedule.py @@ -286,16 +286,39 @@ def _load_old(path: Path) -> dict: return {} +def _lock_summary(lf) -> None: + """Exclusive lock on the summary lock file (flock on POSIX, msvcrt on Windows).""" + if os.name == "nt": + import msvcrt + + lf.seek(0) + msvcrt.locking(lf.fileno(), msvcrt.LK_LOCK, 1) + else: + import fcntl + + fcntl.flock(lf, fcntl.LOCK_EX) + + +def _unlock_summary(lf) -> None: + if os.name == "nt": + import msvcrt + + lf.seek(0) + msvcrt.locking(lf.fileno(), msvcrt.LK_UNLCK, 1) + else: + import fcntl + + fcntl.flock(lf, fcntl.LOCK_UN) + + def _write_summary(summary_path: Path, res: TaskResult) -> None: """Merge `res` into the summary under an flock, atomically, deriving the merged status. Modes may be written by separate processes, so records accumulate per mode. """ - import fcntl - lock = summary_path.with_suffix(".lock") with open(lock, "w") as lf: - fcntl.flock(lf, fcntl.LOCK_EX) + _lock_summary(lf) try: merged = res.to_dict() if summary_path.is_file(): @@ -325,7 +348,7 @@ def _write_summary(summary_path: Path, res: TaskResult) -> None: tmp.write_text(json.dumps(merged, indent=2, default=str)) os.replace(tmp, summary_path) finally: - fcntl.flock(lf, fcntl.LOCK_UN) + _unlock_summary(lf) def _resolve_libraries( diff --git a/harness2/core/tree.py b/harness2/core/tree.py index c9bf1341..fdaaa2ff 100644 --- a/harness2/core/tree.py +++ b/harness2/core/tree.py @@ -137,7 +137,7 @@ def read_tree(surface: SurfaceSpec, d: Path | str, *, rejected: list[str] | None for p in sorted(d.rglob("*")): if not p.is_file() or "__pycache__" in p.parts: continue - rel = str(p.relative_to(d)) + rel = normalize_rel(str(p.relative_to(d))) if rel in stubs: continue if not is_allowed(surface, rel): @@ -164,7 +164,7 @@ def write_tree(surface: SurfaceSpec, tree: Tree, dest: Path | str, *, stubs: boo for p in sorted(dest.rglob("*"), reverse=True): if not p.is_file(): continue - rel = str(p.relative_to(dest)) + rel = normalize_rel(str(p.relative_to(dest))) if is_allowed(surface, rel) or rel in stub_set: p.unlink() for a in surface.always_on: