sched: add held cross-process ownership with real-process tests (T11)

This commit is contained in:
lda
2026-09-08 10:46:17 +07:00 Verified
parent 558f0c3e95
commit f1dbe54f61
2 changed files with 221 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
"""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,
)
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)],
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()