Skip to content

Commit 1768432

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

3 files changed

Lines changed: 37 additions & 17 deletions

File tree

tests/test_updater_ng.py

Lines changed: 0 additions & 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,6 @@ 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-
400397
if errout:
401398
self.fail(
402399
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: 36 additions & 13 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,44 @@
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:
8790
# Windows file locking
8891
import msvcrt
8992

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

98122

99123
class Updater:
@@ -171,9 +195,9 @@ def _lock_metadata(self) -> Iterator[None]:
171195
# Ensure the whole metadata directory structure exists
172196
rootdir = Path(self._dir, "root_history")
173197
rootdir.mkdir(exist_ok=True, parents=True)
198+
174199
logger.debug("Getting metadata lock...")
175-
with open(os.path.join(self._dir, ".lock"), "wb") as f:
176-
_lock_file(f)
200+
with _lock_file(os.path.join(self._dir, ".lock")):
177201
yield
178202
logger.debug("Released metadata lock")
179203

@@ -336,8 +360,7 @@ def download_target(
336360
targetinfo.verify_length_and_hashes(target_file)
337361

338362
target_file.seek(0)
339-
with open(filepath, "wb") as destination_file:
340-
_lock_file(destination_file)
363+
with _lock_file(filepath) as destination_file:
341364
shutil.copyfileobj(target_file, destination_file)
342365

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

0 commit comments

Comments
 (0)