@@ -21,6 +21,15 @@ def _wait_for_file(path: Path, timeout: float = 5.0) -> None:
2121 time .sleep (0.01 )
2222 raise AssertionError (f"timed out waiting for { path } " )
2323
24+
25+ def _assert_file_stays_absent (path : Path , timeout : float = 0.5 ) -> None :
26+ """While parent holds the lock, child must not write *path* (still blocked)."""
27+ deadline = time .monotonic () + timeout
28+ while time .monotonic () < deadline :
29+ if path .is_file ():
30+ raise AssertionError (f"{ path } appeared while parent still holds the lock" )
31+ time .sleep (0.01 )
32+
2433from utils .export_state_store import (
2534 atomic_write_export_state ,
2635 export_state_lock ,
@@ -128,33 +137,41 @@ def test_export_state_lock_blocks_second_process(tmp_path: Path) -> None:
128137 """While the parent holds ``msvcrt`` lock, a child process cannot acquire it."""
129138 state_file = tmp_path / "export_state.json"
130139 state_file .write_text ("{}" , encoding = "utf-8" )
131- started_marker = tmp_path / "child_started .lock"
140+ attempting_marker = tmp_path / "child_attempting .lock"
132141 acquired_marker = tmp_path / "child_acquired.lock"
133142 child = (
134143 "import sys\n "
135144 "from pathlib import Path\n "
136145 f"sys.path.insert(0, { str (REPO_ROOT )!r} )\n "
137146 "from utils.export_state_store import export_state_lock\n "
138- "path, started , acquired = sys.argv[1], sys.argv[2], sys.argv[3]\n "
139- "Path(started ).write_text('ok', encoding='utf-8')\n "
147+ "path, attempting , acquired = sys.argv[1], sys.argv[2], sys.argv[3]\n "
148+ "Path(attempting ).write_text('ok', encoding='utf-8')\n "
140149 "with export_state_lock(path):\n "
141150 " Path(acquired).write_text('ok', encoding='utf-8')\n "
142151 )
143- with export_state_lock (str (state_file )):
144- proc = subprocess .Popen (
145- [
146- sys .executable ,
147- "-c" ,
148- child ,
149- str (state_file ),
150- str (started_marker ),
151- str (acquired_marker ),
152- ],
153- cwd = str (REPO_ROOT ),
154- )
155- _wait_for_file (started_marker )
156- assert not acquired_marker .is_file (), (
157- "child acquired lock while parent still holds it"
158- )
159- assert proc .wait (timeout = 10 ) == 0
160- assert acquired_marker .read_text (encoding = "utf-8" ) == "ok"
152+ proc = subprocess .Popen (
153+ [
154+ sys .executable ,
155+ "-c" ,
156+ child ,
157+ str (state_file ),
158+ str (attempting_marker ),
159+ str (acquired_marker ),
160+ ],
161+ cwd = str (REPO_ROOT ),
162+ )
163+ try :
164+ with export_state_lock (str (state_file )):
165+ _wait_for_file (attempting_marker )
166+ _assert_file_stays_absent (acquired_marker )
167+ try :
168+ assert proc .wait (timeout = 10 ) == 0
169+ except subprocess .TimeoutExpired :
170+ proc .kill ()
171+ proc .wait ()
172+ raise
173+ assert acquired_marker .read_text (encoding = "utf-8" ) == "ok"
174+ finally :
175+ if proc .poll () is None :
176+ proc .kill ()
177+ proc .wait ()
0 commit comments