Skip to content

Commit d8fc5bf

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

File tree

2 files changed

+33
-16
lines changed

2 files changed

+33
-16
lines changed

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}"

tuf/ngclient/updater.py

Lines changed: 33 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,41 @@
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 OSError as e:
109+
if locked:
110+
# yield raised
111+
raise e
112+
err = e
113+
logger.warning("Unsuccessful lock attempt: %s", e)
114+
time.sleep(0.3)
115+
116+
if err:
117+
raise err
97118

98119

99120
class Updater:
@@ -171,9 +192,9 @@ def _lock_metadata(self) -> Iterator[None]:
171192
# Ensure the whole metadata directory structure exists
172193
rootdir = Path(self._dir, "root_history")
173194
rootdir.mkdir(exist_ok=True, parents=True)
195+
174196
logger.debug("Getting metadata lock...")
175-
with open(os.path.join(self._dir, ".lock"), "wb") as f:
176-
_lock_file(f)
197+
with _lock_file(os.path.join(self._dir, ".lock")):
177198
yield
178199
logger.debug("Released metadata lock")
179200

@@ -336,8 +357,7 @@ def download_target(
336357
targetinfo.verify_length_and_hashes(target_file)
337358

338359
target_file.seek(0)
339-
with open(filepath, "wb") as destination_file:
340-
_lock_file(destination_file)
360+
with _lock_file(filepath) as destination_file:
341361
shutil.copyfileobj(target_file, destination_file)
342362

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

0 commit comments

Comments
 (0)