Skip to content

Commit b636a8c

Browse files
committed
WIP another windows test
Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
1 parent 8aa0212 commit b636a8c

File tree

2 files changed

+40
-17
lines changed

2 files changed

+40
-17
lines changed

tests/test_updater_ng.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,6 @@ def test_parallel_updaters(self) -> None:
362362
# Refresh many updaters in parallel many times, using the same local metadata cache.
363363
# This should reveal race conditions.
364364

365-
# windows on GitHub actions *will* fail with "[Errno 36] Resource deadlock avoided"
366-
# if iterations is in the hundreds
367365
iterations = 50
368366
process_count = 10
369367

@@ -396,7 +394,7 @@ def test_parallel_updaters(self) -> None:
396394
errout += "Parallel Refresh script failed:"
397395
errout += f"\nprocess stdout: \n{stdout.decode()}"
398396
errout += f"\nprocess stderr: \n{stderr.decode()}"
399-
397+
print(stdout.decode())
400398
if errout:
401399
self.fail(
402400
f"One or more scripts failed parallel refresh test:\n{errout}"

tuf/ngclient/updater.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import os
5959
import shutil
6060
import tempfile
61+
import time
6162
from pathlib import Path
6263
from typing import IO, TYPE_CHECKING, cast
6364
from urllib import parse
@@ -79,21 +80,46 @@
7980
# advisory file locking for posix
8081
import fcntl
8182

82-
def _lock_file(f: IO) -> None:
83-
if f.writable():
83+
@contextlib.contextmanager
84+
def _lock_file(path: str) -> Iterator[IO]:
85+
with open(path, "wb") as f:
8486
fcntl.lockf(f, fcntl.LOCK_EX)
87+
yield f
8588

8689
except ModuleNotFoundError:
87-
# Windows file locking
90+
# Windows file locking, belt-and-suspenders style:
91+
# Use loop that tries
8892
import msvcrt
8993

90-
def _lock_file(f: IO) -> None:
91-
# On Windows we lock a byte range and file must not be empty
92-
f.write(b"\0")
93-
f.flush()
94-
f.seek(0)
95-
96-
msvcrt.locking(f.fileno(), msvcrt.LK_LOCK, 1)
94+
@contextlib.contextmanager
95+
def _lock_file(path: str) -> Iterator[IO]:
96+
err = None
97+
locked = False
98+
for _ in range(100):
99+
try:
100+
with open(path, "wb") as f:
101+
# file must not be empty
102+
f.write(b"\0")
103+
f.flush()
104+
f.seek(0)
105+
msvcrt.locking(f.fileno(), msvcrt.LK_LOCK, 1)
106+
locked = True
107+
yield f
108+
return
109+
except FileNotFoundError:
110+
# could be from yield or from open() -- either way we bail
111+
raise
112+
except OSError as e:
113+
if locked:
114+
# yield has raised, let's not continue loop
115+
raise e
116+
err = e
117+
logger.warning("Unsuccessful lock attempt for %s: %s", path, e)
118+
time.sleep(0.3)
119+
120+
# raise the last failure if we never got a lock
121+
if err is not None:
122+
raise err
97123

98124

99125
class Updater:
@@ -171,9 +197,9 @@ def _lock_metadata(self) -> Iterator[None]:
171197
# Ensure the whole metadata directory structure exists
172198
rootdir = Path(self._dir, "root_history")
173199
rootdir.mkdir(exist_ok=True, parents=True)
200+
174201
logger.debug("Getting metadata lock...")
175-
with open(os.path.join(self._dir, ".lock"), "wb") as f:
176-
_lock_file(f)
202+
with _lock_file(os.path.join(self._dir, ".lock")):
177203
yield
178204
logger.debug("Released metadata lock")
179205

@@ -336,8 +362,7 @@ def download_target(
336362
targetinfo.verify_length_and_hashes(target_file)
337363

338364
target_file.seek(0)
339-
with open(filepath, "wb") as destination_file:
340-
_lock_file(destination_file)
365+
with _lock_file(filepath) as destination_file:
341366
shutil.copyfileobj(target_file, destination_file)
342367

343368
logger.debug("Downloaded target %s", targetinfo.path)

0 commit comments

Comments
 (0)