From 8d2df0b6518cd18d1ffb6318926092004dae4d2d Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 6 Oct 2026 12:34:31 +0200 Subject: [PATCH] fix: lock the pack directory while splitting --- scripts/split_pack.py | 17 ++++++++++++----- tests/test_split_pack.py | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/scripts/split_pack.py b/scripts/split_pack.py index 8ed3bb6c..55400af9 100644 --- a/scripts/split_pack.py +++ b/scripts/split_pack.py @@ -21,6 +21,8 @@ import sys import zipfile from pathlib import Path +from artifacts import ArtifactLockBusy, artifact_lock + # "Each file included in a release must be under 2 GiB." ASSET_LIMIT = 2 * 1024**3 @@ -222,12 +224,17 @@ def main() -> int: help="a part stays under this many bytes (K, M, G suffixes; default 2G)", ) args = parser.parse_args() + # The packs are rewritten in place by generate_pack under this lock: a + # build running beside the split mixes its bytes into the pack being + # read, or writes a pack that the split then deletes. + directory = args.target if args.target.is_dir() else args.target.parent try: - if args.target.is_dir(): - published = split_directory(args.target, args.max_size) - else: - published = split_pack(args.target, args.max_size) - except (OSError, ValueError, zipfile.BadZipFile) as exc: + with artifact_lock(str(directory)): + if args.target.is_dir(): + published = split_directory(args.target, args.max_size) + else: + published = split_pack(args.target, args.max_size) + except (ArtifactLockBusy, OSError, ValueError, zipfile.BadZipFile) as exc: print(f"Error: {exc}", file=sys.stderr) return 1 for path in published: diff --git a/tests/test_split_pack.py b/tests/test_split_pack.py index 8ac11bbe..e6c9ccfe 100644 --- a/tests/test_split_pack.py +++ b/tests/test_split_pack.py @@ -135,6 +135,32 @@ class PartsAreArchives(SplitFixture): self.assertTrue(modes["bin/engine"] & stat.S_IXUSR) +class SplitHoldsTheDirectory(SplitFixture): + """split_pack read and deleted packs in a directory a build was writing: + the build could truncate the pack under the split, or the split delete + the pack the build had just written.""" + + def test_a_held_directory_is_left_alone(self): + import subprocess + + from artifacts import artifact_lock + + for target in (self.dist, self.pack): + with self.subTest(target=target.name), artifact_lock(str(self.dist)): + proc = subprocess.run( + [sys.executable, "scripts/split_pack.py", str(target), + "--max-size", str(LIMIT)], + cwd=str(REPO_ROOT), capture_output=True, text=True, + timeout=60, check=False, + ) + self.assertEqual(proc.returncode, 1, proc.stdout + proc.stderr) + self.assertIn("in use by another run", proc.stderr) + self.assertTrue(self.pack.exists()) + self.assertEqual( + sorted(p.name for p in self.dist.glob("*.zip")), [self.pack.name] + ) + + class PartsAreNotTakenForPacks(SplitFixture): def test_a_part_is_recognised_by_its_name(self): self.assertTrue(split_pack.is_part("RetroArch_BIOS_Pack.part1of2.zip"))