From ad3e8c33d44426e69167db7dbedfbe08f8bd8ea4 Mon Sep 17 00:00:00 2001 From: Anionix Date: Fri, 24 Jul 2026 15:10:24 +0900 Subject: [PATCH] fix: keep shard output leaf exclusive (#496) --- src/format_bench/shards.py | 6 +++++- tests/test_shards.py | 12 +++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/format_bench/shards.py b/src/format_bench/shards.py index 9e984f5..87b850c 100644 --- a/src/format_bench/shards.py +++ b/src/format_bench/shards.py @@ -138,7 +138,7 @@ def _hardlink_tree(source: Path, destination: Path) -> None: def _mkdir_private_tree(destination: Path) -> None: missing: list[Path] = [] - current = destination + current = destination.parent while not current.exists(): missing.append(current) parent = current.parent @@ -152,6 +152,10 @@ def _mkdir_private_tree(destination: Path) -> None: # parent creation before the private output leaf exists. for directory in reversed(missing): directory.mkdir(mode=0o700) + # LLM contract: OUTPUT_ABSENT -> OUTPUT_CREATED | COLLISION_REJECTED. + # The final leaf stays exclusive even if another process creates it after + # merge validation but before publication begins. + destination.mkdir(mode=0o700) def _copy_run_files(base_run: Path, output_run: Path) -> None: diff --git a/tests/test_shards.py b/tests/test_shards.py index 39df1c9..d6cf6b0 100644 --- a/tests/test_shards.py +++ b/tests/test_shards.py @@ -7,7 +7,7 @@ import pytest -from format_bench.shards import merge_equivalence_shards +from format_bench.shards import _mkdir_private_tree, merge_equivalence_shards def _write(path: Path, value: object) -> None: @@ -111,6 +111,16 @@ def _shard_results(pair: str, name: str) -> dict: } +def test_private_output_tree_rejects_race_created_leaf(tmp_path: Path) -> None: + output = tmp_path / "missing" / "nested" / "merged" + output.mkdir(parents=True) + + with pytest.raises(FileExistsError): + _mkdir_private_tree(output) + + assert list(output.iterdir()) == [] + + @pytest.mark.skipif(os.name != "posix", reason="POSIX directory-mode contract") @pytest.mark.parametrize("mask", (0o002, 0o000)) def test_merge_equivalence_shards_reuses_artifacts_and_unions_results(