11# SPDX-License-Identifier: LGPL-3.0-or-later
22"""Helpers for preparing converted TensorFlow graph files in LAMMPS tests."""
33
4- from __future__ import (
5- annotations ,
6- )
4+ from __future__ import annotations
75
6+ import errno
87import os
98import subprocess as sp
109import sys
1110import tempfile
1211import time
13- from pathlib import (
14- Path ,
15- )
12+ from pathlib import Path
1613
1714_LOCK_TIMEOUT_SECONDS = 60.0
1815_LOCK_POLL_SECONDS = 0.1
@@ -22,13 +19,52 @@ def _is_up_to_date(source: Path, output: Path) -> bool:
2219 return output .exists () and output .stat ().st_mtime_ns >= source .stat ().st_mtime_ns
2320
2421
22+ def _read_lock_pid (lock_file : Path ) -> int | None :
23+ try :
24+ for line in lock_file .read_text (encoding = "utf-8" ).splitlines ():
25+ if line .startswith ("pid=" ):
26+ return int (line .split ("=" , maxsplit = 1 )[1 ])
27+ except (FileNotFoundError , ValueError ):
28+ return None
29+ return None
30+
31+
32+ def _pid_is_running (pid : int ) -> bool :
33+ try :
34+ os .kill (pid , 0 )
35+ except ProcessLookupError :
36+ return False
37+ except PermissionError :
38+ return True
39+ except OSError as err :
40+ if err .errno == errno .ESRCH :
41+ return False
42+ raise
43+ return True
44+
45+
46+ def _should_break_stale_lock (lock_file : Path ) -> bool :
47+ try :
48+ lock_stat = lock_file .stat ()
49+ except FileNotFoundError :
50+ return False
51+
52+ lock_age = time .time () - lock_stat .st_mtime
53+ if lock_age > _LOCK_TIMEOUT_SECONDS :
54+ return True
55+
56+ lock_pid = _read_lock_pid (lock_file )
57+ return lock_pid is not None and not _pid_is_running (lock_pid )
58+
59+
2560def ensure_converted_pb (source : Path , output : Path ) -> Path :
2661 """Convert ``source`` into ``output`` only when the target is missing or stale.
2762
2863 The conversion is protected by a simple lock file and uses atomic replacement so
2964 repeated imports across multiple test modules do not regenerate the same model
3065 more than once.
3166 """
67+
3268 source = source .resolve ()
3369 output = output .resolve ()
3470 output .parent .mkdir (parents = True , exist_ok = True )
@@ -41,6 +77,9 @@ def ensure_converted_pb(source: Path, output: Path) -> Path:
4177 try :
4278 fd = os .open (str (lock_file ), os .O_CREAT | os .O_EXCL | os .O_WRONLY )
4379 except FileExistsError as err :
80+ if _should_break_stale_lock (lock_file ):
81+ lock_file .unlink (missing_ok = True )
82+ continue
4483 if time .monotonic () - started >= _LOCK_TIMEOUT_SECONDS :
4584 raise TimeoutError (f"Timed out waiting for { lock_file } " ) from err
4685 time .sleep (_LOCK_POLL_SECONDS )
@@ -61,7 +100,7 @@ def ensure_converted_pb(source: Path, output: Path) -> Path:
61100 )
62101 os .close (tmp_fd )
63102 tmp_path = Path (tmp_name )
64- sp .check_output (
103+ sp .run (
65104 [
66105 sys .executable ,
67106 "-m" ,
@@ -72,7 +111,8 @@ def ensure_converted_pb(source: Path, output: Path) -> Path:
72111 str (source ),
73112 "-o" ,
74113 str (tmp_path ),
75- ]
114+ ],
115+ check = True ,
76116 )
77117 tmp_path .replace (output )
78118 tmp_path = None
0 commit comments