Skip to content

Commit ac11716

Browse files
authored
fix: lock on Unix during experimental editable.rebuild (#968)
1 parent 1dfec53 commit ac11716

File tree

1 file changed

+94
-34
lines changed

1 file changed

+94
-34
lines changed

src/scikit_build_core/resources/_editable_redirect.py

Lines changed: 94 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,59 @@ def __dir__() -> list[str]:
2222
return __all__
2323

2424

25+
class FileLockIfUnix:
26+
def __init__(self, lock_file: str) -> None:
27+
self.lock_file = lock_file
28+
self.lock_file_fd: int | None = None
29+
30+
def acquire(self) -> None:
31+
# Based on filelock.BaseFileLock.acquire and filelock.UnixFileLock._acquire
32+
try:
33+
import fcntl
34+
except ImportError:
35+
return
36+
import contextlib
37+
import time
38+
39+
poll_interval = 0.05
40+
log_interval = 60
41+
last_log = time.perf_counter()
42+
43+
while True:
44+
os.makedirs(os.path.dirname(self.lock_file), exist_ok=True)
45+
open_flags = os.O_RDWR | os.O_TRUNC
46+
if not os.path.exists(self.lock_file):
47+
open_flags |= os.O_CREAT
48+
49+
fd = os.open(self.lock_file, open_flags, 0o644)
50+
with contextlib.suppress(PermissionError): # Lock is not owned by this UID
51+
os.fchmod(fd, 0o644)
52+
try:
53+
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
54+
except OSError:
55+
os.close(fd)
56+
else:
57+
self.lock_file_fd = fd
58+
return
59+
60+
now = time.perf_counter()
61+
if now - last_log > log_interval:
62+
last_log = now
63+
print(f"Still waiting to acquire lock {self.lock_file}...") # noqa: T201
64+
65+
time.sleep(poll_interval)
66+
67+
def release(self) -> None:
68+
try:
69+
import fcntl
70+
except ImportError:
71+
return
72+
73+
assert isinstance(self.lock_file_fd, int)
74+
fcntl.flock(self.lock_file_fd, fcntl.LOCK_UN)
75+
os.close(self.lock_file_fd)
76+
77+
2578
class ScikitBuildRedirectingFinder(importlib.abc.MetaPathFinder):
2679
def __init__(
2780
self,
@@ -128,42 +181,49 @@ def rebuild(self) -> None:
128181
if verbose:
129182
print(f"Running cmake --build & --install in {self.path}") # noqa: T201
130183

131-
result = subprocess.run(
132-
["cmake", "--build", ".", *self.build_options],
133-
cwd=self.path,
134-
stdout=sys.stderr if verbose else subprocess.PIPE,
135-
env=env,
136-
check=False,
137-
text=True,
138-
)
139-
if result.returncode and verbose:
140-
print( # noqa: T201
141-
f"ERROR: {result.stdout}",
142-
file=sys.stderr,
184+
lock = FileLockIfUnix(os.path.join(self.path, "editable_rebuild.lock"))
185+
186+
try:
187+
lock.acquire()
188+
189+
result = subprocess.run(
190+
["cmake", "--build", ".", *self.build_options],
191+
cwd=self.path,
192+
stdout=sys.stderr if verbose else subprocess.PIPE,
193+
env=env,
194+
check=False,
195+
text=True,
143196
)
144-
result.check_returncode()
145-
146-
result = subprocess.run(
147-
[
148-
"cmake",
149-
"--install",
150-
".",
151-
"--prefix",
152-
self.install_dir,
153-
*self.install_options,
154-
],
155-
cwd=self.path,
156-
stdout=sys.stderr if verbose else subprocess.PIPE,
157-
env=env,
158-
check=False,
159-
text=True,
160-
)
161-
if result.returncode and verbose:
162-
print( # noqa: T201
163-
f"ERROR: {result.stdout}",
164-
file=sys.stderr,
197+
if result.returncode and verbose:
198+
print( # noqa: T201
199+
f"ERROR: {result.stdout}",
200+
file=sys.stderr,
201+
)
202+
result.check_returncode()
203+
204+
result = subprocess.run(
205+
[
206+
"cmake",
207+
"--install",
208+
".",
209+
"--prefix",
210+
self.install_dir,
211+
*self.install_options,
212+
],
213+
cwd=self.path,
214+
stdout=sys.stderr if verbose else subprocess.PIPE,
215+
env=env,
216+
check=False,
217+
text=True,
165218
)
166-
result.check_returncode()
219+
if result.returncode and verbose:
220+
print( # noqa: T201
221+
f"ERROR: {result.stdout}",
222+
file=sys.stderr,
223+
)
224+
result.check_returncode()
225+
finally:
226+
lock.release()
167227

168228

169229
def install(

0 commit comments

Comments
 (0)