"""Exclusive ownership with real competing processes (T11).""" from __future__ import annotations import subprocess import sys import time from pathlib import Path import pytest from wf_scheduling.ownership import ( SchedulerOwnership, SecondOwnerError, ) def test_second_owner_rejected_while_held(tmp_path: Path) -> None: first = SchedulerOwnership(tmp_path, owner="proc-A").acquire() try: with pytest.raises(SecondOwnerError): SchedulerOwnership(tmp_path, owner="proc-B").acquire() finally: first.release() # After release, a new owner acquires. second = SchedulerOwnership(tmp_path, owner="proc-B").acquire() second.release() def test_competing_process_cannot_acquire_while_held(tmp_path: Path) -> None: holder = SchedulerOwnership(tmp_path, owner="parent").acquire() try: script_path = tmp_path / "compete.py" script_path.write_text( "import sys\n" "sys.path.insert(0, '.')\n" "from pathlib import Path\n" "from wf_scheduling.ownership import SchedulerOwnership, SecondOwnerError\n" f"root = Path({str(tmp_path)!r})\n" "try:\n" " SchedulerOwnership(root, owner='child').acquire()\n" "except SecondOwnerError:\n" " print('second-owner-rejected')\n" " raise SystemExit(0)\n" "print('unexpectedly-acquired')\n" "raise SystemExit(1)\n", encoding="utf-8", ) proc = subprocess.run( [sys.executable, str(script_path)], capture_output=True, text=True, timeout=30, cwd=Path(__file__).resolve().parents[2], ) assert proc.returncode == 0, proc.stderr assert "second-owner-rejected" in proc.stdout finally: holder.release() def test_lock_released_on_process_death(tmp_path: Path) -> None: script_path = tmp_path / "hold.py" script_path.write_text( "import sys, time\n" "sys.path.insert(0, '.')\n" "from pathlib import Path\n" "from wf_scheduling.ownership import SchedulerOwnership\n" f"root = Path({str(tmp_path)!r})\n" "own = SchedulerOwnership(root, owner='dying').acquire()\n" "print('acquired', flush=True)\n" "time.sleep(30)\n", encoding="utf-8", ) proc = subprocess.Popen( [sys.executable, str(script_path)], cwd=Path(__file__).resolve().parents[2], stdout=subprocess.PIPE, text=True, ) try: assert proc.stdout is not None line = proc.stdout.readline() assert "acquired" in line # Competing acquisition fails while the child lives. with pytest.raises(SecondOwnerError): SchedulerOwnership(tmp_path, owner="parent").acquire() finally: proc.kill() proc.wait(timeout=30) # After process death the OS releases the held lock; allow a beat. acquired = None for _ in range(50): try: acquired = SchedulerOwnership(tmp_path, owner="parent").acquire() break except SecondOwnerError: time.sleep(0.1) assert acquired is not None acquired.release()