From 1c613dc4b4545f9c352edf834cc039e0939dc1bf Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 27 Oct 2026 21:08:47 +0100 Subject: [PATCH] fix: take the probe lock per standalone run standalone_probe now holds the host probe lock for one run, its load check included, as core_probe does, and the docs no longer wrap region_check or standalone_probe in flock. --- scripts/standalone_probe.py | 7 +++++++ tests/test_standalone_probe.py | 24 ++++++++++++++++++++++++ wiki/tools.md | 10 +++++----- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/scripts/standalone_probe.py b/scripts/standalone_probe.py index ac1e67a9d..9a945be15 100644 --- a/scripts/standalone_probe.py +++ b/scripts/standalone_probe.py @@ -510,6 +510,13 @@ def probe(args: argparse.Namespace) -> int: for problem in problems: print(f"ERROR: {problem}", file=sys.stderr) return 2 + # One run on the host at a time, as core_probe: the lock is held for + # this run, its load check included, and freed when it ends. + with cp.host_probe_lock(): + return _probe(args, emu) + + +def _probe(args: argparse.Namespace, emu: Emulator) -> int: meta, tree = installed(emu, args.sha256) max_load = args.max_load if args.max_load is not None else (os.cpu_count() or 1) / 4 load_before = cp.load_average() diff --git a/tests/test_standalone_probe.py b/tests/test_standalone_probe.py index 73763f648..6a51f731c 100644 --- a/tests/test_standalone_probe.py +++ b/tests/test_standalone_probe.py @@ -157,6 +157,30 @@ class Oracle(unittest.TestCase): self.assertEqual(result["verdicts"], {"absent": "bios_error", "scph5501.bin": "boots"}) +class HostLock(unittest.TestCase): + def test_a_run_holds_the_probe_lock_for_itself(self) -> None: + import contextlib + from unittest import mock + + events = [] + + @contextlib.contextmanager + def lock(): + events.append("take") + yield + events.append("free") + + def body(_args, _emu): + events.append("run") + return 0 + + with tempfile.NamedTemporaryFile(suffix=".cue") as content, \ + mock.patch.object(sp.cp, "host_probe_lock", lock), mock.patch.object(sp, "_probe", body): + args = sp.build_parser().parse_args(["run", "duckstation", "--content", content.name]) + self.assertEqual(sp.probe(args), 0) + self.assertEqual(events, ["take", "run", "free"]) + + class CommandLine(unittest.TestCase): def test_check_refuses_two_destinations(self) -> None: with tempfile.NamedTemporaryFile(suffix=".cue") as content: diff --git a/wiki/tools.md b/wiki/tools.md index cbcb32c4b..a9b205bb8 100644 --- a/wiki/tools.md +++ b/wiki/tools.md @@ -1098,7 +1098,7 @@ times, each a record whose subject is `region---`. | `pack` | the files of `generate_pack.py --emulator E --region R`, where the pack puts them | ```bash -flock ~/.local/state/retrobios/probe.lock python scripts/region_check.py run \ +python scripts/region_check.py run \ --emulator beetle_psx --system sony-playstation --core-name mednafen_psx python scripts/region_check.py report # the outcome of every region record, as JSON ``` @@ -1120,7 +1120,7 @@ frame for a game, a variant counts as booting when it samples the same frames as the `full` run, and the outcome says it was judged that way. The system pages show each outcome as a sentence ("the Japanese BIOS boots the Japanese game, the other regions' BIOS do not") with links to the four records. A -maintainer starts each run under the probe lock. Nothing runs by itself. +maintainer starts each run. Each core_probe run takes the host probe lock for itself and frees it before the next, so do not wrap the command in `flock`. Nothing runs by itself. ### standalone_probe.py @@ -1134,11 +1134,11 @@ sandbox, oracle and record functions, imported and not copied. ```bash python scripts/standalone_probe.py resolve duckstation # the asset EmuDeck's installer picks python scripts/standalone_probe.py fetch duckstation # download and unpack it, by sha256 -flock ~/.local/state/retrobios/probe.lock python scripts/standalone_probe.py run duckstation \ +python scripts/standalone_probe.py run duckstation \ --content game.cue --content-label "Bust-A-Move 2 - Arcade Edition (USA)" \ --bios-file scph5501.bin=bios/Sony/PlayStation/scph5501.bin \ --samples 60 --interval 1.5 --require-sandbox --record -flock ~/.local/state/retrobios/probe.lock python scripts/standalone_probe.py check pcsx2 \ +python scripts/standalone_probe.py check pcsx2 \ --content game.bin --vary ps2-0230a-20080220.bin= \ --vary "ps2-0230a-20080220.bin=bios/Sony/PlayStation 2/ps2-0230a-20080220.bin" \ --claim "..." --method "..." --require-sandbox --record @@ -1168,7 +1168,7 @@ Supported: DuckStation and PCSX2. Dolphin, Flycast, PPSSPP, melonDS, xemu and MAME are installed by EmuDeck from Flathub, Ryujinx from a Codeberg tarball and RPCS3 from its own update server: each needs its installer's rule before it can be added. Cemu is an AppImage like the two supported ones, and waits for -Wii U content, which no catalog romfetch reads holds. A maintainer starts each run under the probe lock. Nothing runs +Wii U content, which no catalog romfetch reads holds. Each run takes the host probe lock (`RETROBIOS_PROBE_LOCK`) for itself, its load check included, and frees it when it ends, as `core_probe.py` does. A maintainer starts each run. Nothing runs by itself. ### Other tools