Skip to content

Commit a182d71

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

File tree

3 files changed

+44
-18
lines changed

3 files changed

+44
-18
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(stderr.decode())
400398
if errout:
401399
self.fail(
402400
f"One or more scripts failed parallel refresh test:\n{errout}"

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ skipsdist = true
1111
[testenv]
1212
commands =
1313
python3 --version
14-
python3 -m coverage run -m unittest
14+
python3 -m coverage run -m unittest -v
1515
python3 -m coverage report -m --fail-under 96
1616

1717
deps =

tuf/ngclient/updater.py

Lines changed: 42 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,49 @@
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+
logger.warning("lock attempt for %s", path)
86+
with open(path, "wb") as f:
8487
fcntl.lockf(f, fcntl.LOCK_EX)
88+
yield f
8589

8690
except ModuleNotFoundError:
87-
# Windows file locking
91+
# Windows file locking, belt-and-suspenders style:
92+
# Use loop that tries to open the lockfile for 30 secs, but also
93+
# use msvcrt.locking(). Dumb but seems to work.
8894
import msvcrt
8995

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

98127

99128
class Updater:
@@ -171,9 +200,9 @@ def _lock_metadata(self) -> Iterator[None]:
171200
# Ensure the whole metadata directory structure exists
172201
rootdir = Path(self._dir, "root_history")
173202
rootdir.mkdir(exist_ok=True, parents=True)
203+
174204
logger.debug("Getting metadata lock...")
175-
with open(os.path.join(self._dir, ".lock"), "wb") as f:
176-
_lock_file(f)
205+
with _lock_file(os.path.join(self._dir, ".lock")):
177206
yield
178207
logger.debug("Released metadata lock")
179208

@@ -336,8 +365,7 @@ def download_target(
336365
targetinfo.verify_length_and_hashes(target_file)
337366

338367
target_file.seek(0)
339-
with open(filepath, "wb") as destination_file:
340-
_lock_file(destination_file)
368+
with _lock_file(filepath) as destination_file:
341369
shutil.copyfileobj(target_file, destination_file)
342370

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

0 commit comments

Comments
 (0)