Skip to content

Commit fc7dd8a

Browse files
committed
WIP test windows weirdness
Signed-off-by: Jussi Kukkonen <jkukkonen@google.com>
1 parent 129243a commit fc7dd8a

File tree

2 files changed

+50
-26
lines changed

2 files changed

+50
-26
lines changed

tests/test_updater_ng.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -359,10 +359,13 @@ def test_user_agent(self) -> None:
359359

360360
class TestParallelUpdater(TestUpdater):
361361
def test_parallel_updaters(self) -> None:
362-
# Refresh two updaters in parallel many times, using the same local metadata cache.
362+
# Refresh many updaters in parallel many times, using the same local metadata cache.
363363
# This should reveal race conditions.
364364

365-
iterations = 1000
365+
# windows on GitHub actions *will* fail with "[Errno 36] Resource deadlock avoided"
366+
# if iterations is in the hundreds
367+
iterations = 50
368+
process_count = 10
366369

367370
# The project root is the parent of the tests directory
368371
project_root = os.path.dirname(utils.TESTS_DIR)
@@ -376,29 +379,27 @@ def test_parallel_updaters(self) -> None:
376379
self.metadata_url,
377380
]
378381

379-
p1 = subprocess.Popen(
380-
command,
381-
stdout=subprocess.PIPE,
382-
stderr=subprocess.PIPE,
383-
cwd=project_root,
384-
)
385-
p2 = subprocess.Popen(
386-
command,
387-
stdout=subprocess.PIPE,
388-
stderr=subprocess.PIPE,
389-
cwd=project_root,
390-
)
382+
procs = [
383+
subprocess.Popen(
384+
command,
385+
stdout=subprocess.PIPE,
386+
stderr=subprocess.PIPE,
387+
cwd=project_root,
388+
)
389+
for _ in range(process_count)
390+
]
391391

392-
stdout1, stderr1 = p1.communicate()
393-
stdout2, stderr2 = p2.communicate()
392+
errout = ""
393+
for proc in procs:
394+
stdout, stderr = proc.communicate()
395+
if proc.returncode != 0:
396+
errout += "Parallel Refresh script failed:"
397+
errout += f"\nprocess stdout: \n{stdout.decode()}"
398+
errout += f"\nprocess stderr: \n{stderr.decode()}"
394399

395-
if p1.returncode != 0 or p2.returncode != 0:
400+
if errout:
396401
self.fail(
397-
"Parallel refresh failed"
398-
f"\nprocess 1 stdout: \n{stdout1.decode()}"
399-
f"\nprocess 1 stderr: \n{stderr1.decode()}"
400-
f"\nprocess 2 stdout: \n{stdout2.decode()}"
401-
f"\nprocess 2 stderr: \n{stderr2.decode()}"
402+
f"One or more scripts failed parallel refresh test:\n{errout}"
402403
)
403404

404405

tuf/ngclient/updater.py

Lines changed: 27 additions & 4 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
@@ -172,11 +173,33 @@ def _lock_metadata(self) -> Iterator[None]:
172173
rootdir = Path(self._dir, "root_history")
173174
rootdir.mkdir(exist_ok=True, parents=True)
174175

175-
with open(os.path.join(self._dir, ".lock"), "wb") as f:
176-
logger.debug("Getting metadata lock...")
177-
_lock_file(f)
176+
lock_path = os.path.join(self._dir, ".lock")
177+
lock_file = None
178+
try:
179+
logger.warning("Getting metadata lock...")
180+
for _ in range(100): # retry 100 times (10 seconds)
181+
try:
182+
# Use 'x+b' for exclusive creation, read/write binary.
183+
# This is atomic: it fails if the file exists.
184+
lock_file = open(lock_path, "x+b") # noqa: SIM115
185+
break
186+
except (FileExistsError, PermissionError):
187+
time.sleep(0.1)
188+
else:
189+
raise exceptions.RepositoryError(
190+
f"Failed to acquire lock {lock_path} after 10 seconds"
191+
)
192+
193+
_lock_file(lock_file)
178194
yield
179-
logger.debug("Releasing metadata lock")
195+
196+
finally:
197+
if lock_file is not None:
198+
lock_file.close()
199+
# The lock is released by deleting the file.
200+
with contextlib.suppress(FileNotFoundError):
201+
os.remove(lock_path)
202+
logger.warning("Released metadata lock")
180203

181204
def refresh(self) -> None:
182205
"""Refresh top-level metadata.

0 commit comments

Comments
 (0)